From c8a89da12b95c20ef0cfe316d36ac47eb2ef9e00 Mon Sep 17 00:00:00 2001 From: wreckage0907 Date: Tue, 5 May 2026 20:51:46 +0530 Subject: [PATCH 001/261] fix: document follow unfollow broken due to PEP 563 lazy annotations bypassing type coercion (cherry picked from commit d0aff16885d8b485cb7080247c298be3efb75397) --- frappe/desk/form/document_follow.py | 6 ++++-- frappe/public/js/frappe/form/toolbar.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/desk/form/document_follow.py b/frappe/desk/form/document_follow.py index c51ac0bea1c5..5dacc4cd99c2 100644 --- a/frappe/desk/form/document_follow.py +++ b/frappe/desk/form/document_follow.py @@ -11,10 +11,12 @@ @frappe.whitelist() def update_follow(doctype: str, doc_name: str, following: bool): + following = frappe.utils.sbool(following) if following: return (follow_document(doctype, doc_name, frappe.session.user) and True) or False else: - return unfollow_document(doctype, doc_name, frappe.session.user) + unfollow_document(doctype, doc_name, frappe.session.user) + return False @frappe.whitelist() @@ -83,7 +85,7 @@ def unfollow_document(doctype, doc_name, user): if doc: frappe.delete_doc("Document Follow", doc[0].name, force=True) frappe.toast(_("Un-following document {0}").format(doc_name)) - return False + return True return False diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 9196e85027f3..099878068bc2 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -944,7 +944,7 @@ frappe.ui.form.Toolbar = class Toolbar { } get_follow_text(follow) { - if (follow === null) { + if (follow == null) { follow = this.frm.get_docinfo().is_document_followed; } return follow ? __("Unfollow") : __("Follow"); From e97f12222afe8bde147753ec393d0d30231d8168 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Wed, 13 May 2026 10:03:32 +0530 Subject: [PATCH 002/261] refactor: simplify return logic in update_follow (cherry picked from commit 4a49614dc67d9c1085240e09fda438e74ab03d35) --- frappe/desk/form/document_follow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/desk/form/document_follow.py b/frappe/desk/form/document_follow.py index 5dacc4cd99c2..675a556b2081 100644 --- a/frappe/desk/form/document_follow.py +++ b/frappe/desk/form/document_follow.py @@ -10,10 +10,11 @@ @frappe.whitelist() -def update_follow(doctype: str, doc_name: str, following: bool): +def update_follow(doctype: str, doc_name: str, following: bool | str): following = frappe.utils.sbool(following) if following: - return (follow_document(doctype, doc_name, frappe.session.user) and True) or False + is_following = follow_document(doctype, doc_name, frappe.session.user) + return bool(is_following) else: unfollow_document(doctype, doc_name, frappe.session.user) return False From aecbc10b5a2e338dfcca149f4665fa7777f1def6 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Wed, 13 May 2026 20:09:26 +0530 Subject: [PATCH 003/261] fix: skip standard report validation in migrate --- frappe/core/doctype/report/report.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index e40550c11e5f..cbc3f2ece9ea 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -411,7 +411,12 @@ def validate_standard_report(self): if frappe.session.user != "Administrator": frappe.throw(_("Only Administrator can save a standard report. Please rename and save.")) - if not cint(frappe.conf.developer_mode): + if not cint(frappe.conf.developer_mode) and not ( + frappe.flags.in_migrate + or frappe.flags.in_patch + or frappe.flags.in_install + or frappe.flags.in_import + ): frappe.throw(_("Standard reports can only be created in developer mode.")) @frappe.whitelist() From 79176f8c19db06537a24d9c3939acbf6b842a9b9 Mon Sep 17 00:00:00 2001 From: Asmita Hase Date: Wed, 6 May 2026 16:42:04 +0530 Subject: [PATCH 004/261] fix: prevent report message from being overwritten by stale report message (cherry picked from commit 20453617bcc832207c4715a22c3bea1ee0ae1e2f) --- frappe/public/js/frappe/views/reports/query_report.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 8ccec0020b4b..5933c4a8aff3 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -827,7 +827,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.render_summary(data.report_summary); } - if (data.message && !data.prepared_report) this.show_status(data.message); + if (data.message && !data.prepared_report) this.show_report_message(data.message); this.toggle_message(false); if (data.result && data.result.length) { @@ -2240,7 +2240,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.$status = $(`
`) .hide() .insertAfter(page_form); - + this.$report_message = $(`
`) + .hide() + .insertAfter(this.$status); this.$summary = $(`
`).hide().appendTo(this.page.main); this.$chart = $('
').hide().appendTo(this.page.main); @@ -2253,7 +2255,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { show_status(status_message) { this.$status.html(status_message).show(); } - + show_report_message(message) { + this.$report_message.html(message).show(); + } hide_status() { this.$status.hide(); } From df1cbd96bb00a272e9044197d0602b216757be6f Mon Sep 17 00:00:00 2001 From: sokumon Date: Mon, 11 May 2026 17:53:01 +0530 Subject: [PATCH 005/261] fix: remove orphan entites before creating new assets (cherry picked from commit e947f5e8403dea3e3813df77349c3a4ddfb74156) --- frappe/patches/v16_0/auto_generate_desktop_icon_and_sidebar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/patches/v16_0/auto_generate_desktop_icon_and_sidebar.py b/frappe/patches/v16_0/auto_generate_desktop_icon_and_sidebar.py index 794438375aae..945df0a3f93e 100644 --- a/frappe/patches/v16_0/auto_generate_desktop_icon_and_sidebar.py +++ b/frappe/patches/v16_0/auto_generate_desktop_icon_and_sidebar.py @@ -1,6 +1,8 @@ +from frappe.model.sync import remove_orphan_entities from frappe.utils.install import auto_generate_icons_and_sidebar def execute(): """Auto Create desktop icons and workspace sidebars.""" + remove_orphan_entities() auto_generate_icons_and_sidebar() From 74c16c3d4571c72535818147213cbce39956086b Mon Sep 17 00:00:00 2001 From: sokumon Date: Mon, 11 May 2026 19:10:20 +0530 Subject: [PATCH 006/261] fix: delete only orphan workspaces (cherry picked from commit 06abc317ad1afede0773201eefda894796274e3d) --- frappe/model/sync.py | 15 +++++++++++---- .../auto_generate_desktop_icon_and_sidebar.py | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 3fe383f162b8..453d53515778 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -199,8 +199,8 @@ def remove_orphan_doctypes(): print() -def remove_orphan_entities(): - entites = ["Workspace", "Dashboard", "Page", "Report", "Notification"] +def remove_orphan_entities(entity_types=None): + entities = ["Workspace", "Dashboard", "Page", "Report", "Notification"] app_level_entities = ["Workspace Sidebar", "Desktop Icon"] entity_filter_map = { "Workspace": [{"public": 1, "module": ["is", "set"], "app": ["is", "set"]}], @@ -211,9 +211,14 @@ def remove_orphan_entities(): "Desktop Icon": {"standard": True}, "Notification": {"is_standard": True}, } - entity_file_map = create_entity_file_map(entites) + entity_file_map = create_entity_file_map(entities) + if entity_types: + if isinstance(entity_types, list): + entities = entity_types + else: + entities = [entity_types] - for entity in entites: + for entity in entities: print(f"Removing orphan {entity}s") all_enitities = frappe.get_all( entity, filters=entity_filter_map.get(entity), fields=["name", "module"] @@ -234,6 +239,8 @@ def remove_orphan_entities(): # save the deleted icons frappe.db.commit() # nosemgrep # Remove app level entities + if entity_types and not set(entity_types).issubset(set(app_level_entities)): + return for app_entity in app_level_entities: print(f"Removing orphan {app_entity}s") all_enitities = frappe.get_all( diff --git a/frappe/patches/v16_0/auto_generate_desktop_icon_and_sidebar.py b/frappe/patches/v16_0/auto_generate_desktop_icon_and_sidebar.py index 945df0a3f93e..a0bdbded4ff1 100644 --- a/frappe/patches/v16_0/auto_generate_desktop_icon_and_sidebar.py +++ b/frappe/patches/v16_0/auto_generate_desktop_icon_and_sidebar.py @@ -4,5 +4,5 @@ def execute(): """Auto Create desktop icons and workspace sidebars.""" - remove_orphan_entities() + remove_orphan_entities("Workspace") auto_generate_icons_and_sidebar() From 475408b04f5871f20c4a657a92c2652ca771fa03 Mon Sep 17 00:00:00 2001 From: Gajendra Nishad <75714258+gajjug004@users.noreply.github.com> Date: Thu, 14 May 2026 16:35:16 +0530 Subject: [PATCH 007/261] fix: guard against empty image_field in get_preview_data (#39275) Co-authored-by: vishwajeet-13 (cherry picked from commit ee87683c08d0cbf5fe617d60a1cfde622da780f2) --- frappe/desk/link_preview.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/link_preview.py b/frappe/desk/link_preview.py index c9143ef5f16a..4d79400b201d 100644 --- a/frappe/desk/link_preview.py +++ b/frappe/desk/link_preview.py @@ -28,7 +28,8 @@ def get_preview_data(doctype, docname): image_field = meta.image_field preview_fields.append(title_field) - preview_fields.append(image_field) + if image_field: + preview_fields.append(image_field) preview_fields.append("name") preview_data = frappe.get_list(doctype, filters={"name": docname}, fields=preview_fields, limit=1) From 14b5d72a92babbb6882c4396082e0111ba27ff3e Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Thu, 14 May 2026 19:37:53 +0530 Subject: [PATCH 008/261] fix: child table link fields break for guest submissions --- frappe/website/doctype/web_form/web_form.py | 45 ++++++++++++++++----- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 957a76d7bb7d..cbd08744da6d 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -431,7 +431,7 @@ def load_translations(self, context): def load_list_data(self, context): if not self.list_columns: - self.list_columns = get_in_list_view_fields(self.doc_type) + self.list_columns = get_in_list_view_fields(self.doc_type, self.name) context.web_form_doc.list_columns = self.list_columns def load_form_data(self, context): @@ -468,7 +468,7 @@ def load_form_data(self, context): # For Table fields, server-side processing for meta for field in context.web_form_doc.web_form_fields: if field.fieldtype == "Table": - field.fields = get_in_list_view_fields(field.options) + field.fields = get_in_list_view_fields(field.options, self.name) if field.fieldtype == "Link": field.fieldtype = "Autocomplete" @@ -809,7 +809,7 @@ def get_form_data(doctype: str, docname: str | None = None, web_form_name: str | # For Table fields, server-side processing for meta for field in out.web_form.web_form_fields: if field.fieldtype == "Table": - field.fields = get_in_list_view_fields(field.options) + field.fields = get_in_list_view_fields(field.options, web_form_name) out.update({field.fieldname: field.fields}) if field.fieldtype == "Link": @@ -822,7 +822,7 @@ def get_form_data(doctype: str, docname: str | None = None, web_form_name: str | @frappe.whitelist() -def get_in_list_view_fields(doctype): +def get_in_list_view_fields(doctype, web_form_name=None): meta = frappe.get_meta(doctype) fields = [] @@ -839,21 +839,48 @@ def get_in_list_view_fields(doctype): def get_field_df(fieldname): if fieldname == "name": return {"label": "Name", "fieldname": "name", "fieldtype": "Data"} - return meta.get_field(fieldname).as_dict() + df = meta.get_field(fieldname).as_dict() + if df.get("options") and df.get("fieldtype") == "Link": + df["fieldtype"] = "Autocomplete" + df["options"] = get_link_options( + web_form_name, + doctype=df.options, + allow_read_on_all_link_options=df.get("allow_read_on_all_link_options", False), + ) + return df return [get_field_df(f) for f in fields] +def has_link_option(fields, doctype): + for f in fields: + if f.options == doctype: + return True + if f.fieldtype == "Table" and f.options: + child_doctype = f.options + if not isinstance(child_doctype, str) or not child_doctype.strip(): + continue + try: + child_table_fields = frappe.get_meta(child_doctype).fields + except Exception: + continue + for child_field in child_table_fields: + if getattr(child_field, "options", None) == doctype: + return True + return False + + def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=False): web_form: WebForm = frappe.get_lazy_doc("Web Form", web_form_name) if web_form.login_required and frappe.session.user == "Guest": frappe.throw(_("You must be logged in to use this form."), frappe.PermissionError) - if not web_form.published or not any(f for f in web_form.web_form_fields if f.options == doctype): - frappe.throw( - _("You don't have permission to access the {0} DocType.").format(doctype), frappe.PermissionError - ) + if not web_form.published or not has_link_option(web_form.web_form_fields, doctype): + frappe.throw( + _("You don't have permission to access the {0} DocType.").format(doctype), + frappe.PermissionError, + ) link_options, filters = [], {} if web_form.login_required and not allow_read_on_all_link_options: From 75c2fde85c314c5f0537f612bc1dc79c73fcaf0d Mon Sep 17 00:00:00 2001 From: KerollesFathy Date: Tue, 12 May 2026 13:43:44 +0000 Subject: [PATCH 009/261] fix: cast read-only field to string on update after submit (cherry picked from commit 5153cbb3bd4243df9715603e03aa8143c0e83ee9) --- frappe/model/base_document.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index f268e314ba66..0d553b80d7a9 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -552,6 +552,9 @@ def get_valid_dict( elif fieldtype in float_like_fields and not isinstance(value, float): value = flt(value) + elif fieldtype == "Read Only" and not isinstance(value, str): + value = cstr(value) + elif (fieldtype in datetime_fields and value == "") or ( getattr(df, "unique", False) and cstr(value).strip() == "" ): From 0824b1104f17457246330c11a64751072812ea6c Mon Sep 17 00:00:00 2001 From: sokumon Date: Wed, 13 May 2026 13:33:38 +0530 Subject: [PATCH 010/261] fix: handle total row csv conversion (cherry picked from commit 122f33e5b8d6b0150c55570eb80f0ef031a0f882) --- frappe/core/doctype/prepared_report/prepared_report.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index dd5f26b4424c..7ef062b63e5f 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -382,7 +382,10 @@ def convert_json_to_csv(prepared_report_name): writer = csv.DictWriter(output, fieldnames=fieldnames) writer.writeheader() for row in result: - writer.writerow({key: row.get(key, "") for key in fieldnames}) + if isinstance(row, dict): + writer.writerow({key: row.get(key, "") for key in fieldnames}) + else: + writer.writerow({field: row[idx] for idx, field in enumerate(fieldnames)}) csv_content = output.getvalue().encode("utf-8") From 81069b77473c40909640f2646a998e66d24fd3d7 Mon Sep 17 00:00:00 2001 From: Vibhuti Garachh <157696107+Vibhuti410@users.noreply.github.com> Date: Fri, 15 May 2026 15:38:48 +0530 Subject: [PATCH 011/261] Keyboard shortcuts (#39297) (cherry picked from commit 3f993a012ff98b459ea702c4d90e2ed3d6cff69c) --- frappe/public/js/frappe/desk.js | 2 +- frappe/public/js/frappe/ui/keyboard.js | 59 ++++++++++++-------------- frappe/public/scss/desk/form.scss | 2 +- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index d1804fb5c32d..f5edccda00b9 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -93,7 +93,7 @@ frappe.Application = class Application { setup_theme() { frappe.ui.keys.add_shortcut({ shortcut: "shift+ctrl+g", - description: __("Switch Theme"), + description: __("Switch theme"), action: () => { if (frappe.theme_switcher && frappe.theme_switcher.dialog.is_visible) { frappe.theme_switcher.hide(); diff --git a/frappe/public/js/frappe/ui/keyboard.js b/frappe/public/js/frappe/ui/keyboard.js index dcb41b1545f8..a07de36a29be 100644 --- a/frappe/public/js/frappe/ui/keyboard.js +++ b/frappe/public/js/frappe/ui/keyboard.js @@ -90,22 +90,33 @@ frappe.ui.keys.show_keyboard_shortcut_dialog = () => { if (!shortcuts.length) { return ""; } - let html = shortcuts + let deduped = []; + let seen = {}; + shortcuts .filter((s) => (s.condition ? s.condition() : true)) .filter((s) => !!s.description) - .map((shortcut) => { - let shortcut_label = shortcut.shortcut - .split("+") - .map(frappe.utils.to_title_case) - .join("+"); - if (frappe.utils.is_mac()) { - shortcut_label = shortcut_label.replace("Ctrl", "⌘").replace("Alt", "⌥"); + .forEach((shortcut) => { + if (seen[shortcut.description] !== undefined) { + deduped[seen[shortcut.description]].keys.push(shortcut.shortcut); + } else { + seen[shortcut.description] = deduped.length; + deduped.push({ ...shortcut, keys: [shortcut.shortcut] }); } - - shortcut_label = shortcut_label.replace("Shift", "⇧"); - + }); + let html = deduped + .map((shortcut) => { + let shortcut_label = shortcut.keys + .map((k) => { + let label = k.split("+").map(frappe.utils.to_title_case).join("+"); + if (frappe.utils.is_mac()) { + label = label.replace("Ctrl", "⌘").replace("Alt", "⌥"); + } + label = label.replace("Shift", "⇧"); + return `${label}`; + }) + .join(" / "); return ` - ${shortcut_label} + ${shortcut_label} ${shortcut.description || ""} `; }) @@ -193,7 +204,7 @@ frappe.ui.keys.add_shortcut({ e.preventDefault(); return false; }, - description: __("Trigger Primary Action"), + description: __("Trigger primary action"), ignore_inputs: true, }); @@ -219,30 +230,12 @@ frappe.ui.keys.add_shortcut({ ignore_inputs: true, }); -frappe.ui.keys.add_shortcut({ - shortcut: "alt+s", - action: function (e) { - e.preventDefault(); - $(".dropdown-navbar-user button").eq(0).click(); - }, - description: __("Open Settings"), -}); - frappe.ui.keys.add_shortcut({ shortcut: "shift+/", action: function () { frappe.ui.keys.show_keyboard_shortcut_dialog(); }, - description: __("Show Keyboard Shortcuts"), -}); - -frappe.ui.keys.add_shortcut({ - shortcut: "alt+h", - action: function (e) { - e.preventDefault(); - $(".dropdown-help button").eq(0).click(); - }, - description: __("Open Help"), + description: __("Show keyboard shortcuts"), }); frappe.ui.keys.on("escape", function (e) { @@ -286,7 +279,7 @@ frappe.ui.keys.add_shortcut({ action: function () { frappe.ui.toolbar.clear_cache(); }, - description: __("Clear Cache and Reload"), + description: __("Clear cache and reload"), }); frappe.ui.keys.key_map = { diff --git a/frappe/public/scss/desk/form.scss b/frappe/public/scss/desk/form.scss index 994045e37709..b4642ee32ee5 100644 --- a/frappe/public/scss/desk/form.scss +++ b/frappe/public/scss/desk/form.scss @@ -32,7 +32,7 @@ padding: 0 var(--padding-md); .form-section-description { max-width: var(--page-max-width); - margin: auto; + margin-left: 0; margin-bottom: 10px; @include get_textstyle("base", "regular"); color: var(--text-light); From 9bb3c40b5b5eb8a4f1d83990a8b7f82d1c93040e Mon Sep 17 00:00:00 2001 From: KerollesFathy Date: Thu, 30 Apr 2026 16:59:08 +0000 Subject: [PATCH 012/261] fix: hide Customize button for core DocTypes (cherry picked from commit 509f8649f3dbec975a48badd9f5b40f470a6d1ff) --- frappe/public/js/frappe/form/toolbar.js | 3 ++- frappe/public/js/frappe/list/list_view.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 099878068bc2..58596b85fbd9 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -630,8 +630,9 @@ frappe.ui.form.Toolbar = class Toolbar { ) { let doctype = is_doctype_form ? this.frm.docname : this.frm.doctype; let is_doctype_custom = is_doctype_form ? this.frm.doc.custom : false; + let is_core_doctype = frappe.model.core_doctypes_list.includes(doctype); - if (doctype != "DocType" && !is_doctype_custom && this.frm.meta.issingle === 0) { + if (!is_core_doctype && !is_doctype_custom && this.frm.meta.issingle === 0) { this.page.add_menu_item( __("Customize"), () => { diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index e58a1bf852e4..0045c31b19d1 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -2003,7 +2003,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if ( frappe.model.can_create("Custom Field") && - frappe.model.can_create("Property Setter") + frappe.model.can_create("Property Setter") && + !frappe.model.core_doctypes_list.includes(doctype) ) { items.push({ label: __("Customize", null, "Button in list view menu"), From 4327f6dd2a52c5ddc278d482e37b2db85a662dc6 Mon Sep 17 00:00:00 2001 From: KerollesFathy Date: Thu, 14 May 2026 19:04:26 +0000 Subject: [PATCH 013/261] fix: prevent duplicate settings button in session defaults dialog (cherry picked from commit 1e2e3a25fae6b78626081f2761eecf559f3cc0b3) --- frappe/public/js/frappe/ui/toolbar/toolbar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js index a70116820e2b..3f1cb125a565 100644 --- a/frappe/public/js/frappe/ui/toolbar/toolbar.js +++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js @@ -289,7 +289,7 @@ frappe.ui.toolbar.fetch_session_defaults = function () { frappe.ui.toolbar.setup_session_defaults = function () { let perms = frappe.perm.get_perm("Session Default Settings"); - let fields = frappe.boot.session_defaults; + let fields = [...frappe.boot.session_defaults]; //add settings button only if user is a System Manager or has permission on 'Session Default Settings' if (frappe.user_roles.includes("System Manager") || perms[0].read == 1) { fields[fields.length] = { From 345016b08a4610be28eaf5ac581d791b406b651f Mon Sep 17 00:00:00 2001 From: sokumon Date: Mon, 18 May 2026 12:22:50 +0530 Subject: [PATCH 014/261] fix: workspace editing condition (cherry picked from commit 6ac326dedb432404897078624dcf2f07f4f9fa20) --- frappe/desk/doctype/workspace/workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index cddafa1290c8..77e505612a6f 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -340,7 +340,7 @@ def save_page(name, public, new_widgets, blocks): public = frappe.parse_json(public) doc = frappe.get_doc("Workspace", name) - if not (is_workspace_manager() and doc.for_user == frappe.session.user): + if not is_workspace_manager() or (not doc.public and doc.for_user != frappe.session.user): return if not doc.type: From 68343cb2e6dc5e751e36beb9529f755acc98910f Mon Sep 17 00:00:00 2001 From: gajjug004 Date: Wed, 13 May 2026 23:44:23 +0530 Subject: [PATCH 015/261] fix: skip self-follow on User doc in add_docshare (cherry picked from commit edfd379e898e1367bd92574f20f78ef77370d848) --- frappe/share.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/share.py b/frappe/share.py index fd8a04385f5e..8eab21dbd92c 100644 --- a/frappe/share.py +++ b/frappe/share.py @@ -71,7 +71,9 @@ def add_docshare( doc.save(ignore_permissions=True) notify_assignment(user, doctype, name, everyone, notify=notify) - if frappe.get_cached_value("User", user, "follow_shared_documents"): + if (user != name or doctype != "User") and frappe.get_cached_value( + "User", user, "follow_shared_documents" + ): follow_document(doctype, name, user) return doc From d828b8a4929ef0d5c63fb91c15df56610e2784c0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 11:15:05 +0200 Subject: [PATCH 016/261] revert: changes to dead path in file uploader (#39248) (#39329) Reverts inconsequential but potentially confusing changes introduced in #33995 (cherry picked from commit 97bf177adc371c74221ca99c98a66b6511137079) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- frappe/core/doctype/file/file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 4fe1e5b869d3..f6b0b2313963 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -302,8 +302,8 @@ def validate_file_url(self): if self.is_remote_file or not self.file_url: return - if not self.file_url.startswith(("/files/", "/private/files/", "/api/method/")): - # Probably an invalid URL since it doesn't start with http and isn't an internal URL either + if not self.file_url.startswith(("/files/", "/private/files/")): + # Probably an invalid URL since it doesn't start with http either frappe.throw( _("URL must start with http:// or https://"), title=_("Invalid URL"), From 8caa55cfbe60e13d2cf90990ec64c80817b8d1cc Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Mon, 18 May 2026 11:34:55 +0530 Subject: [PATCH 017/261] fix(login): disallow header override when generating temp. login link (cherry picked from commit 52a68eaf97226c291e2cf1a0883c0f2bd0750ea9) --- frappe/www/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/www/login.py b/frappe/www/login.py index aec0db361d60..25a35b270100 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -181,7 +181,7 @@ def _generate_temporary_login_link(email: str, expiry: int): key = frappe.generate_hash() frappe.cache.set_value(f"one_time_login_key:{key}", email, expires_in_sec=expiry * 60) - return get_url(f"/api/method/frappe.www.login.login_via_key?key={key}") + return get_url(f"/api/method/frappe.www.login.login_via_key?key={key}", allow_header_override=False) @frappe.whitelist(allow_guest=True, methods=["GET"]) From 493a2d47497ff5b54fecc1975734b9fb85accf29 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Mon, 18 May 2026 12:33:07 +0530 Subject: [PATCH 018/261] fix(safe_exec): block queries that export records This is more of a DiD (defence in depth) fix. Now explicitly check whether the query is exporting results via Outfile/Dumpfile. (cherry picked from commit 5abd3eeb1483db202304184917bd925d68378a51) # Conflicts: # frappe/utils/safe_exec.py --- frappe/utils/safe_exec.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index c60274488ac8..cd6c0eaabe48 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -477,6 +477,8 @@ def read_sql(query, *args, **kwargs): def check_safe_sql_query(query: str, throw: bool = True) -> bool: + import re + """Check if SQL query is safe for running in restricted context. Safe queries: @@ -487,9 +489,22 @@ def check_safe_sql_query(query: str, throw: bool = True) -> bool: query = query.strip().lower() whitelisted_statements = ("select", "explain") +<<<<<<< HEAD if query.startswith(whitelisted_statements) or ( query.startswith("with") and frappe.db.db_type == "mariadb" ): +======= + if re.search(r"\binto\s+(outfile|dumpfile)\b", query): + if throw: + frappe.throw( + _("Read-Only queries are allowed"), + title=_("Unsafe SQL query"), + exc=frappe.PermissionError, + ) + return False + + if query.startswith(whitelisted_statements): +>>>>>>> 5abd3eeb14 (fix(safe_exec): block queries that export records) return True if throw: From 55f4b50fb816d2db61e10beef261a12eccf77daa Mon Sep 17 00:00:00 2001 From: Asmita Hase Date: Thu, 14 May 2026 12:28:00 +0530 Subject: [PATCH 019/261] fix: toggle report message when filters change (cherry picked from commit d157a49f3f7a4089d81561cb6e8f71c0903321e5) --- frappe/public/js/frappe/views/reports/query_report.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 5933c4a8aff3..734140606fd6 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -2380,6 +2380,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.$report.toggle(flag); this.$chart.toggle(flag); this.$summary.toggle(flag); + this.$report_message.toggle(flag); } toggle_print_buttons(show) { From bb289ff3229863f7494aa70c2740648ff1b44e3d Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Mon, 18 May 2026 17:22:34 +0530 Subject: [PATCH 020/261] chore: resolve conflicts --- frappe/utils/safe_exec.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index cd6c0eaabe48..951eb2db310a 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -489,11 +489,6 @@ def check_safe_sql_query(query: str, throw: bool = True) -> bool: query = query.strip().lower() whitelisted_statements = ("select", "explain") -<<<<<<< HEAD - if query.startswith(whitelisted_statements) or ( - query.startswith("with") and frappe.db.db_type == "mariadb" - ): -======= if re.search(r"\binto\s+(outfile|dumpfile)\b", query): if throw: frappe.throw( @@ -503,8 +498,9 @@ def check_safe_sql_query(query: str, throw: bool = True) -> bool: ) return False - if query.startswith(whitelisted_statements): ->>>>>>> 5abd3eeb14 (fix(safe_exec): block queries that export records) + if query.startswith(whitelisted_statements) or ( + query.startswith("with") and frappe.db.db_type == "mariadb" + ): return True if throw: From 4d2d79462052ee1b0f689cf8d34bcf4a2e78fce3 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal <166826779+ShrihariMahabal@users.noreply.github.com> Date: Mon, 18 May 2026 17:45:23 +0530 Subject: [PATCH 021/261] Revert "fix: child table link fields break for guest submissions" --- frappe/website/doctype/web_form/web_form.py | 45 +++++---------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index cbd08744da6d..957a76d7bb7d 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -431,7 +431,7 @@ def load_translations(self, context): def load_list_data(self, context): if not self.list_columns: - self.list_columns = get_in_list_view_fields(self.doc_type, self.name) + self.list_columns = get_in_list_view_fields(self.doc_type) context.web_form_doc.list_columns = self.list_columns def load_form_data(self, context): @@ -468,7 +468,7 @@ def load_form_data(self, context): # For Table fields, server-side processing for meta for field in context.web_form_doc.web_form_fields: if field.fieldtype == "Table": - field.fields = get_in_list_view_fields(field.options, self.name) + field.fields = get_in_list_view_fields(field.options) if field.fieldtype == "Link": field.fieldtype = "Autocomplete" @@ -809,7 +809,7 @@ def get_form_data(doctype: str, docname: str | None = None, web_form_name: str | # For Table fields, server-side processing for meta for field in out.web_form.web_form_fields: if field.fieldtype == "Table": - field.fields = get_in_list_view_fields(field.options, web_form_name) + field.fields = get_in_list_view_fields(field.options) out.update({field.fieldname: field.fields}) if field.fieldtype == "Link": @@ -822,7 +822,7 @@ def get_form_data(doctype: str, docname: str | None = None, web_form_name: str | @frappe.whitelist() -def get_in_list_view_fields(doctype, web_form_name=None): +def get_in_list_view_fields(doctype): meta = frappe.get_meta(doctype) fields = [] @@ -839,48 +839,21 @@ def get_in_list_view_fields(doctype, web_form_name=None): def get_field_df(fieldname): if fieldname == "name": return {"label": "Name", "fieldname": "name", "fieldtype": "Data"} - df = meta.get_field(fieldname).as_dict() - if df.get("options") and df.get("fieldtype") == "Link": - df["fieldtype"] = "Autocomplete" - df["options"] = get_link_options( - web_form_name, - doctype=df.options, - allow_read_on_all_link_options=df.get("allow_read_on_all_link_options", False), - ) - return df + return meta.get_field(fieldname).as_dict() return [get_field_df(f) for f in fields] -def has_link_option(fields, doctype): - for f in fields: - if f.options == doctype: - return True - if f.fieldtype == "Table" and f.options: - child_doctype = f.options - if not isinstance(child_doctype, str) or not child_doctype.strip(): - continue - try: - child_table_fields = frappe.get_meta(child_doctype).fields - except Exception: - continue - for child_field in child_table_fields: - if getattr(child_field, "options", None) == doctype: - return True - return False - - def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=False): web_form: WebForm = frappe.get_lazy_doc("Web Form", web_form_name) if web_form.login_required and frappe.session.user == "Guest": frappe.throw(_("You must be logged in to use this form."), frappe.PermissionError) - if not web_form.published or not has_link_option(web_form.web_form_fields, doctype): - frappe.throw( - _("You don't have permission to access the {0} DocType.").format(doctype), - frappe.PermissionError, - ) + if not web_form.published or not any(f for f in web_form.web_form_fields if f.options == doctype): + frappe.throw( + _("You don't have permission to access the {0} DocType.").format(doctype), frappe.PermissionError + ) link_options, filters = [], {} if web_form.login_required and not allow_read_on_all_link_options: From a29c93baef10106db8762d12a7bac99cd00981e2 Mon Sep 17 00:00:00 2001 From: Shrihari Mahabal Date: Mon, 18 May 2026 17:57:13 +0530 Subject: [PATCH 022/261] fix: perm check indentation in web form --- frappe/website/doctype/web_form/web_form.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index cbd08744da6d..d82ff22af3c7 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -876,11 +876,11 @@ def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=Fals if web_form.login_required and frappe.session.user == "Guest": frappe.throw(_("You must be logged in to use this form."), frappe.PermissionError) - if not web_form.published or not has_link_option(web_form.web_form_fields, doctype): - frappe.throw( - _("You don't have permission to access the {0} DocType.").format(doctype), - frappe.PermissionError, - ) + if not web_form.published or not has_link_option(web_form.web_form_fields, doctype): + frappe.throw( + _("You don't have permission to access the {0} DocType.").format(doctype), + frappe.PermissionError, + ) link_options, filters = [], {} if web_form.login_required and not allow_read_on_all_link_options: From e1fd139e9a110561354248bfeae0634fe99e4183 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 18 Feb 2026 14:54:52 +0530 Subject: [PATCH 023/261] fix: use autocomplete for showing child table links in web form --- frappe/website/doctype/web_form/web_form.py | 37 ++++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 957a76d7bb7d..64c2acbe3a00 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -431,7 +431,7 @@ def load_translations(self, context): def load_list_data(self, context): if not self.list_columns: - self.list_columns = get_in_list_view_fields(self.doc_type) + self.list_columns = get_in_list_view_fields(self.doc_type, self.name) context.web_form_doc.list_columns = self.list_columns def load_form_data(self, context): @@ -468,7 +468,7 @@ def load_form_data(self, context): # For Table fields, server-side processing for meta for field in context.web_form_doc.web_form_fields: if field.fieldtype == "Table": - field.fields = get_in_list_view_fields(field.options) + field.fields = get_in_list_view_fields(field.options, self.name) if field.fieldtype == "Link": field.fieldtype = "Autocomplete" @@ -809,7 +809,7 @@ def get_form_data(doctype: str, docname: str | None = None, web_form_name: str | # For Table fields, server-side processing for meta for field in out.web_form.web_form_fields: if field.fieldtype == "Table": - field.fields = get_in_list_view_fields(field.options) + field.fields = get_in_list_view_fields(field.options, web_form_name) out.update({field.fieldname: field.fields}) if field.fieldtype == "Link": @@ -822,7 +822,7 @@ def get_form_data(doctype: str, docname: str | None = None, web_form_name: str | @frappe.whitelist() -def get_in_list_view_fields(doctype): +def get_in_list_view_fields(doctype, web_form_name=None): meta = frappe.get_meta(doctype) fields = [] @@ -839,21 +839,40 @@ def get_in_list_view_fields(doctype): def get_field_df(fieldname): if fieldname == "name": return {"label": "Name", "fieldname": "name", "fieldtype": "Data"} - return meta.get_field(fieldname).as_dict() + df = meta.get_field(fieldname).as_dict() + if df.get("options") and df.get("fieldtype") == "Link": + df["fieldtype"] = "Autocomplete" + df["options"] = get_link_options( + web_form_name, + doctype=df.options, + allow_read_on_all_link_options=df.get("allow_read_on_all_link_options", False), + ) + return df return [get_field_df(f) for f in fields] +def has_link_option(fields, doctype): + for f in fields: + if f.options == doctype: + return True + if hasattr(f, "fields") and isinstance(f.fields, list): + if has_link_option(f.fields, doctype): + return True + return False + + def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=False): web_form: WebForm = frappe.get_lazy_doc("Web Form", web_form_name) if web_form.login_required and frappe.session.user == "Guest": frappe.throw(_("You must be logged in to use this form."), frappe.PermissionError) - if not web_form.published or not any(f for f in web_form.web_form_fields if f.options == doctype): - frappe.throw( - _("You don't have permission to access the {0} DocType.").format(doctype), frappe.PermissionError - ) + if not web_form.published or not has_link_option(web_form.web_form_fields, doctype): + frappe.throw( + _("You don't have permission to access the {0} DocType.").format(doctype), + frappe.PermissionError, + ) link_options, filters = [], {} if web_form.login_required and not allow_read_on_all_link_options: From 2d6900764e561432d68265b936b5ad3b28b5f07e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 18 Feb 2026 20:03:38 +0530 Subject: [PATCH 024/261] refactor: cleanup process link field --- frappe/website/doctype/web_form/web_form.py | 36 ++++++++++----------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 64c2acbe3a00..740063c0f5fb 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -471,10 +471,7 @@ def load_form_data(self, context): field.fields = get_in_list_view_fields(field.options, self.name) if field.fieldtype == "Link": - field.fieldtype = "Autocomplete" - field.options = get_link_options( - self.name, field.options, field.allow_read_on_all_link_options - ) + process_link_field(field, self.name) context.reference_doc = {} @@ -623,6 +620,14 @@ def _add_attachment(attachment): return permitted_attachments +def process_link_field(field, web_form_name): + field.fieldtype = "Autocomplete" + field.options = get_link_options( + web_form_name, field.options, getattr(field, "allow_read_on_all_link_options", False) + ) + return field + + def get_web_form_module(doc): if doc.is_standard: return get_doc_module(doc.module, doc.doctype, doc.name) @@ -813,10 +818,7 @@ def get_form_data(doctype: str, docname: str | None = None, web_form_name: str | out.update({field.fieldname: field.fields}) if field.fieldtype == "Link": - field.fieldtype = "Autocomplete" - field.options = get_link_options( - web_form_name, field.options, field.allow_read_on_all_link_options - ) + process_link_field(field, web_form_name) return out @@ -839,14 +841,10 @@ def get_in_list_view_fields(doctype, web_form_name=None): def get_field_df(fieldname): if fieldname == "name": return {"label": "Name", "fieldname": "name", "fieldtype": "Data"} + df = meta.get_field(fieldname).as_dict() if df.get("options") and df.get("fieldtype") == "Link": - df["fieldtype"] = "Autocomplete" - df["options"] = get_link_options( - web_form_name, - doctype=df.options, - allow_read_on_all_link_options=df.get("allow_read_on_all_link_options", False), - ) + process_link_field(df, web_form_name) return df return [get_field_df(f) for f in fields] @@ -868,11 +866,11 @@ def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=Fals if web_form.login_required and frappe.session.user == "Guest": frappe.throw(_("You must be logged in to use this form."), frappe.PermissionError) - if not web_form.published or not has_link_option(web_form.web_form_fields, doctype): - frappe.throw( - _("You don't have permission to access the {0} DocType.").format(doctype), - frappe.PermissionError, - ) + if not web_form.published or not has_link_option(web_form.web_form_fields, doctype): + frappe.throw( + _("You don't have permission to access the {0} DocType.").format(doctype), + frappe.PermissionError, + ) link_options, filters = [], {} if web_form.login_required and not allow_read_on_all_link_options: From 319b262c6c8e52f500686e9eb66b7aa667daa45f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 19 Feb 2026 13:03:21 +0530 Subject: [PATCH 025/261] chore: remove unused whitelist decorator --- frappe/website/doctype/web_form/web_form.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 740063c0f5fb..1f3e3156e375 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -823,7 +823,6 @@ def get_form_data(doctype: str, docname: str | None = None, web_form_name: str | return out -@frappe.whitelist() def get_in_list_view_fields(doctype, web_form_name=None): meta = frappe.get_meta(doctype) fields = [] From 3ae72309b013118649b50189803565dbed0607a6 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Tue, 24 Feb 2026 09:49:20 +0530 Subject: [PATCH 026/261] fix: only check for valid child table fields to compare doctypes --- cypress/integration/web_form.js | 6 ++---- frappe/public/js/frappe/form/controls/attach.js | 4 ++-- frappe/website/doctype/web_form/web_form.py | 14 +++++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/cypress/integration/web_form.js b/cypress/integration/web_form.js index 5f1c4474a5fb..36f65a80bc79 100644 --- a/cypress/integration/web_form.js +++ b/cypress/integration/web_form.js @@ -72,10 +72,8 @@ context("Web Form", () => { cy.call("logout"); - cy.visit("/note"); - cy.get_open_dialog() - .get(".modal-message") - .contains("You are not permitted to access this page without login."); + cy.visit("/note", { failOnStatusCode: false }); + cy.contains("You must be logged in to use this form."); }); it("Show List", () => { diff --git a/frappe/public/js/frappe/form/controls/attach.js b/frappe/public/js/frappe/form/controls/attach.js index b7fb99072e68..35e16d6d8ef2 100644 --- a/frappe/public/js/frappe/form/controls/attach.js +++ b/frappe/public/js/frappe/form/controls/attach.js @@ -125,8 +125,8 @@ frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.Contro .attr("href", dataurl || this.value); } } else { - this.$input.toggle(true); - this.$value.toggle(false); + this.$input?.toggle(true); + this.$value?.toggle(false); } } diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 1f3e3156e375..772f864373bf 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -853,9 +853,17 @@ def has_link_option(fields, doctype): for f in fields: if f.options == doctype: return True - if hasattr(f, "fields") and isinstance(f.fields, list): - if has_link_option(f.fields, doctype): - return True + if f.fieldtype == "Table" and f.options: + child_doctype = f.options + if not isinstance(child_doctype, str) or not child_doctype.strip(): + continue + try: + child_table_fields = frappe.get_meta(child_doctype).fields + except Exception: + continue + for child_field in child_table_fields: + if getattr(child_field, "options", None) == doctype: + return True return False From 3c702aee8080231d5324189e13d960ac15fc7465 Mon Sep 17 00:00:00 2001 From: MochaMind Date: Mon, 18 May 2026 23:26:59 +0530 Subject: [PATCH 027/261] chore: update POT file (#39191) --- frappe/locale/main.pot | 317 +++++++++++++++++++++-------------------- 1 file changed, 161 insertions(+), 156 deletions(-) diff --git a/frappe/locale/main.pot b/frappe/locale/main.pot index 2c13a4c9975c..fc9afe134681 100644 --- a/frappe/locale/main.pot +++ b/frappe/locale/main.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: Frappe Framework VERSION\n" "Report-Msgid-Bugs-To: developers@frappe.io\n" -"POT-Creation-Date: 2026-05-03 09:54+0000\n" -"PO-Revision-Date: 2026-05-03 09:54+0000\n" +"POT-Creation-Date: 2026-05-10 09:57+0000\n" +"PO-Revision-Date: 2026-05-10 09:57+0000\n" "Last-Translator: developers@frappe.io\n" "Language-Team: developers@frappe.io\n" "MIME-Version: 1.0\n" @@ -68,7 +68,7 @@ msgstr "" msgid "<head> HTML" msgstr "" -#: frappe/database/query.py:2355 +#: frappe/database/query.py:2356 msgid "'*' is only allowed in {0} SQL function(s)" msgstr "" @@ -162,11 +162,11 @@ msgstr "" msgid "1 Google Calendar Event synced." msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:996 +#: frappe/public/js/frappe/views/reports/query_report.js:997 msgid "1 Report" msgstr "" -#: frappe/tests/test_utils.py:906 +#: frappe/tests/test_utils.py:930 msgid "1 day ago" msgstr "" @@ -175,17 +175,17 @@ msgid "1 hour" msgstr "" #: frappe/public/js/frappe/utils/pretty_date.js:54 -#: frappe/tests/test_utils.py:904 +#: frappe/tests/test_utils.py:928 msgid "1 hour ago" msgstr "" #: frappe/public/js/frappe/utils/pretty_date.js:50 -#: frappe/tests/test_utils.py:902 +#: frappe/tests/test_utils.py:926 msgid "1 minute ago" msgstr "" #: frappe/public/js/frappe/utils/pretty_date.js:68 -#: frappe/tests/test_utils.py:910 +#: frappe/tests/test_utils.py:934 msgid "1 month ago" msgstr "" @@ -207,37 +207,37 @@ msgctxt "User added row to child table" msgid "1 row to {0}" msgstr "" -#: frappe/tests/test_utils.py:901 +#: frappe/tests/test_utils.py:925 msgid "1 second ago" msgstr "" #: frappe/public/js/frappe/utils/pretty_date.js:64 -#: frappe/tests/test_utils.py:908 +#: frappe/tests/test_utils.py:932 msgid "1 week ago" msgstr "" #: frappe/public/js/frappe/utils/pretty_date.js:72 -#: frappe/tests/test_utils.py:912 +#: frappe/tests/test_utils.py:936 msgid "1 year ago" msgstr "" -#: frappe/tests/test_utils.py:905 +#: frappe/tests/test_utils.py:929 msgid "2 hours ago" msgstr "" -#: frappe/tests/test_utils.py:911 +#: frappe/tests/test_utils.py:935 msgid "2 months ago" msgstr "" -#: frappe/tests/test_utils.py:909 +#: frappe/tests/test_utils.py:933 msgid "2 weeks ago" msgstr "" -#: frappe/tests/test_utils.py:913 +#: frappe/tests/test_utils.py:937 msgid "2 years ago" msgstr "" -#: frappe/tests/test_utils.py:903 +#: frappe/tests/test_utils.py:927 msgid "3 minutes ago" msgstr "" @@ -253,7 +253,7 @@ msgstr "" msgid "5 Records" msgstr "" -#: frappe/tests/test_utils.py:907 +#: frappe/tests/test_utils.py:931 msgid "5 days ago" msgstr "" @@ -1016,7 +1016,7 @@ msgstr "" #: frappe/public/js/frappe/views/reports/query_report.js:192 #: frappe/public/js/frappe/views/reports/query_report.js:205 #: frappe/public/js/frappe/views/reports/query_report.js:215 -#: frappe/public/js/frappe/views/reports/query_report.js:898 +#: frappe/public/js/frappe/views/reports/query_report.js:899 msgid "Actions" msgstr "" @@ -1138,8 +1138,8 @@ msgid "Add Child" msgstr "" #: frappe/public/js/frappe/views/kanban/kanban_board.html:4 -#: frappe/public/js/frappe/views/reports/query_report.js:1978 -#: frappe/public/js/frappe/views/reports/query_report.js:1981 +#: frappe/public/js/frappe/views/reports/query_report.js:1979 +#: frappe/public/js/frappe/views/reports/query_report.js:1982 #: frappe/public/js/frappe/views/reports/report_view.js:367 #: frappe/public/js/frappe/views/reports/report_view.js:392 #: frappe/public/js/print_format_builder/Field.vue:112 @@ -1205,7 +1205,7 @@ msgstr "" msgid "Add Reply-To header" msgstr "" -#: frappe/core/doctype/user/user.py:890 +#: frappe/core/doctype/user/user.py:894 msgid "Add Roles" msgstr "" @@ -1541,11 +1541,11 @@ msgstr "" msgid "Administrator" msgstr "" -#: frappe/core/doctype/user/user.py:1316 +#: frappe/core/doctype/user/user.py:1320 msgid "Administrator Logged In" msgstr "" -#: frappe/core/doctype/user/user.py:1310 +#: frappe/core/doctype/user/user.py:1314 msgid "Administrator accessed {0} on {1} via IP Address {2}." msgstr "" @@ -1648,7 +1648,7 @@ msgstr "" msgid "Alert" msgstr "" -#: frappe/database/query.py:2404 +#: frappe/database/query.py:2405 msgid "Alias must be a string" msgstr "" @@ -2132,7 +2132,7 @@ msgstr "" msgid "Allows users to enable the mask property for any field of the respective doctype." msgstr "" -#: frappe/core/doctype/user/user.py:1117 +#: frappe/core/doctype/user/user.py:1121 msgid "Already Registered" msgstr "" @@ -2564,7 +2564,7 @@ msgstr "" msgid "Are you sure you want to discard the changes?" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1012 +#: frappe/public/js/frappe/views/reports/query_report.js:1013 msgid "Are you sure you want to generate a new report?" msgstr "" @@ -2588,6 +2588,10 @@ msgstr "" msgid "Are you sure you want to relink this communication to {0}?" msgstr "" +#: frappe/core/doctype/communication/communication.js:166 +msgid "Are you sure you want to relink this communication?" +msgstr "" + #: frappe/core/doctype/rq_job/rq_job_list.js:10 msgid "Are you sure you want to remove all failed jobs?" msgstr "" @@ -3614,7 +3618,7 @@ msgstr "" msgid "Beta" msgstr "" -#: frappe/core/doctype/user/user.py:1333 frappe/utils/password_strength.py:73 +#: frappe/core/doctype/user/user.py:1337 frappe/utils/password_strength.py:73 msgid "Better add a few more letters or another word" msgstr "" @@ -4682,7 +4686,7 @@ msgstr "" msgid "Choose Existing Card or create New Card" msgstr "" -#: frappe/public/js/frappe/views/workspace/workspace.js:652 +#: frappe/public/js/frappe/views/workspace/workspace.js:653 msgid "Choose a block or continue typing" msgstr "" @@ -4808,7 +4812,7 @@ msgstr "" msgid "Click on the link below to verify your request" msgstr "" -#: frappe/public/js/frappe/views/workspace/workspace.js:721 +#: frappe/public/js/frappe/views/workspace/workspace.js:722 msgid "Click on {0} to edit" msgstr "" @@ -5016,7 +5020,7 @@ msgctxt "Shrink code field." msgid "Collapse" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2274 +#: frappe/public/js/frappe/views/reports/query_report.js:2275 #: frappe/public/js/frappe/views/treeview.js:124 msgid "Collapse All" msgstr "" @@ -5073,7 +5077,7 @@ msgstr "" #: frappe/desk/doctype/number_card/number_card.json #: frappe/desk/doctype/todo/todo.json #: frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json -#: frappe/public/js/frappe/views/reports/query_report.js:1293 +#: frappe/public/js/frappe/views/reports/query_report.js:1294 #: frappe/public/js/frappe/widgets/widget_dialog.js:546 #: frappe/public/js/frappe/widgets/widget_dialog.js:694 #: frappe/website/doctype/color/color.json @@ -5326,7 +5330,7 @@ msgstr "" msgid "Complete By" msgstr "" -#: frappe/core/doctype/user/user.py:544 +#: frappe/core/doctype/user/user.py:548 #: frappe/templates/emails/new_user.html:10 msgid "Complete Registration" msgstr "" @@ -5889,7 +5893,7 @@ msgstr "" #: frappe/public/js/frappe/list/list_filter.js:121 #: frappe/public/js/frappe/views/file/file_view.js:118 #: frappe/public/js/frappe/views/interaction.js:18 -#: frappe/public/js/frappe/views/reports/query_report.js:1325 +#: frappe/public/js/frappe/views/reports/query_report.js:1326 #: frappe/public/js/frappe/views/workspace/workspace.js:517 #: frappe/workflow/page/workflow_builder/workflow_builder.js:46 msgid "Create" @@ -5909,7 +5913,7 @@ msgid "Create Card" msgstr "" #: frappe/public/js/frappe/views/reports/query_report.js:286 -#: frappe/public/js/frappe/views/reports/query_report.js:1252 +#: frappe/public/js/frappe/views/reports/query_report.js:1253 msgid "Create Chart" msgstr "" @@ -6229,7 +6233,7 @@ msgstr "" msgid "Custom Document Types (Select Permission)" msgstr "" -#: frappe/desk/desktop.py:479 +#: frappe/desk/desktop.py:476 msgid "Custom Documents" msgstr "" @@ -6322,7 +6326,7 @@ msgstr "" msgid "Custom Report" msgstr "" -#: frappe/desk/desktop.py:480 +#: frappe/desk/desktop.py:477 msgid "Custom Reports" msgstr "" @@ -7162,7 +7166,7 @@ msgstr "" msgid "Delete all {0} rows" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:979 +#: frappe/public/js/frappe/views/reports/query_report.js:980 msgid "Delete and Generate New" msgstr "" @@ -8258,7 +8262,7 @@ msgstr "" msgid "Document is only editable by users with role" msgstr "" -#: frappe/core/doctype/communication/communication.js:182 +#: frappe/core/doctype/communication/communication.js:185 msgid "Document not Relinked" msgstr "" @@ -8282,10 +8286,6 @@ msgstr "" msgid "Document {0} has been set to state {1} by {2}" msgstr "" -#: frappe/public/js/frappe/form/controls/base_input.js:232 -msgid "Documentation" -msgstr "" - #. Label of the documentation (Data) field in DocType 'DocType' #: frappe/core/doctype/doctype/doctype.json msgid "Documentation Link" @@ -8422,7 +8422,7 @@ msgstr "" msgid "Download PDF" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:888 +#: frappe/public/js/frappe/views/reports/query_report.js:889 msgid "Download Report" msgstr "" @@ -8631,8 +8631,8 @@ msgstr "" #: frappe/public/js/frappe/form/templates/contact_list.html:13 #: frappe/public/js/frappe/form/toolbar.js:214 #: frappe/public/js/frappe/form/toolbar.js:790 -#: frappe/public/js/frappe/views/reports/query_report.js:914 -#: frappe/public/js/frappe/views/reports/query_report.js:1924 +#: frappe/public/js/frappe/views/reports/query_report.js:915 +#: frappe/public/js/frappe/views/reports/query_report.js:1925 #: frappe/public/js/frappe/widgets/base_widget.js:65 #: frappe/public/js/frappe/widgets/chart_widget.js:304 #: frappe/public/js/frappe/widgets/number_card_widget.js:365 @@ -8901,7 +8901,7 @@ msgstr "" #. Label of the email_account (Link) field in DocType 'Email Queue' #. Label of the email_account (Link) field in DocType 'Unhandled Email' #. Label of a Workspace Sidebar Item -#: frappe/core/doctype/communication/communication.js:199 +#: frappe/core/doctype/communication/communication.js:202 #: frappe/core/doctype/communication/communication.json #: frappe/core/doctype/user_email/user_email.json #: frappe/email/doctype/email_account/email_account.json @@ -8921,7 +8921,7 @@ msgstr "" msgid "Email Account Name" msgstr "" -#: frappe/core/doctype/user/user.py:820 +#: frappe/core/doctype/user/user.py:824 msgid "Email Account added multiple times" msgstr "" @@ -9117,11 +9117,11 @@ msgstr "" msgid "Email Unsubscribe" msgstr "" -#: frappe/core/doctype/communication/communication.js:342 +#: frappe/core/doctype/communication/communication.js:345 msgid "Email has been marked as spam" msgstr "" -#: frappe/core/doctype/communication/communication.js:355 +#: frappe/core/doctype/communication/communication.js:358 msgid "Email has been moved to trash" msgstr "" @@ -9180,7 +9180,7 @@ msgstr "" msgid "Embed code copied" msgstr "" -#: frappe/database/query.py:2408 +#: frappe/database/query.py:2409 msgid "Empty alias is not allowed" msgstr "" @@ -9188,7 +9188,7 @@ msgstr "" msgid "Empty column" msgstr "" -#: frappe/database/query.py:2349 +#: frappe/database/query.py:2350 msgid "Empty string arguments are not allowed" msgstr "" @@ -9869,7 +9869,7 @@ msgstr "" msgid "Executing..." msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2290 +#: frappe/public/js/frappe/views/reports/query_report.js:2291 msgid "Execution Time: {0} sec" msgstr "" @@ -9895,7 +9895,7 @@ msgctxt "Enlarge code field." msgid "Expand" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2272 +#: frappe/public/js/frappe/views/reports/query_report.js:2273 #: frappe/public/js/frappe/views/treeview.js:134 msgid "Expand All" msgstr "" @@ -9969,7 +9969,7 @@ msgstr "" #: frappe/core/page/permission_manager/permission_manager_help.html:66 #: frappe/public/js/frappe/data_import/data_exporter.js:92 #: frappe/public/js/frappe/data_import/data_exporter.js:247 -#: frappe/public/js/frappe/views/reports/query_report.js:1966 +#: frappe/public/js/frappe/views/reports/query_report.js:1967 #: frappe/public/js/frappe/views/reports/report_view.js:1731 #: frappe/public/js/frappe/widgets/chart_widget.js:320 msgid "Export" @@ -10358,7 +10358,7 @@ msgstr "" #: frappe/public/js/frappe/list/bulk_operations.js:327 #: frappe/public/js/frappe/list/list_view_permission_restrictions.html:3 #: frappe/public/js/frappe/views/reports/query_report.js:237 -#: frappe/public/js/frappe/views/reports/query_report.js:2025 +#: frappe/public/js/frappe/views/reports/query_report.js:2026 #: frappe/website/doctype/web_form_field/web_form_field.json #: frappe/website/doctype/web_form_list_column/web_form_list_column.json msgid "Field" @@ -11157,7 +11157,7 @@ msgid "" "For ranges, use 5:10 (for values between 5 & 10)." msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2287 +#: frappe/public/js/frappe/views/reports/query_report.js:2288 msgid "For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10)." msgstr "" @@ -11449,7 +11449,7 @@ msgstr "" msgid "From Date Field" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1986 +#: frappe/public/js/frappe/views/reports/query_report.js:1987 msgid "From Document Type" msgstr "" @@ -11516,7 +11516,7 @@ msgstr "" msgid "Function {0} is not whitelisted." msgstr "" -#: frappe/database/query.py:2253 +#: frappe/database/query.py:2254 msgid "Function {0} requires arguments but none were provided" msgstr "" @@ -11524,7 +11524,7 @@ msgstr "" msgid "Further sub-groups can only be created under records marked as 'Group'" msgstr "" -#: frappe/core/doctype/communication/communication.js:291 +#: frappe/core/doctype/communication/communication.js:294 msgid "Fw: {0}" msgstr "" @@ -11581,7 +11581,7 @@ msgstr "" msgid "Generate Keys" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:908 +#: frappe/public/js/frappe/views/reports/query_report.js:909 msgid "Generate New Report" msgstr "" @@ -12366,7 +12366,7 @@ msgstr "" msgid "Hidden Fields" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1774 +#: frappe/public/js/frappe/views/reports/query_report.js:1775 msgid "Hidden columns include:
{0}" msgstr "" @@ -12922,7 +12922,7 @@ msgstr "" msgid "If these instructions where not helpful, please add in your suggestions on GitHub Issues." msgstr "" -#: frappe/core/doctype/user/user.py:1185 +#: frappe/core/doctype/user/user.py:1189 msgid "If this email is registered with us, we have sent password reset instructions to it. Please check your inbox." msgstr "" @@ -13372,15 +13372,15 @@ msgid "Include Web View Link in Email" msgstr "" #: frappe/public/js/frappe/form/print_utils.js:60 -#: frappe/public/js/frappe/views/reports/query_report.js:1748 +#: frappe/public/js/frappe/views/reports/query_report.js:1749 msgid "Include filters" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1770 +#: frappe/public/js/frappe/views/reports/query_report.js:1771 msgid "Include hidden columns" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1740 +#: frappe/public/js/frappe/views/reports/query_report.js:1741 msgid "Include indentation" msgstr "" @@ -13550,7 +13550,7 @@ msgstr "" #. Label of the insert_after (Select) field in DocType 'Custom Field' #: frappe/custom/doctype/custom_field/custom_field.json -#: frappe/public/js/frappe/views/reports/query_report.js:2031 +#: frappe/public/js/frappe/views/reports/query_report.js:2032 msgid "Insert After" msgstr "" @@ -13624,7 +13624,7 @@ msgstr "" msgid "Insufficient Permission Level for {0}" msgstr "" -#: frappe/database/query.py:1368 +#: frappe/database/query.py:1369 msgid "Insufficient Permission for {0}" msgstr "" @@ -13925,7 +13925,7 @@ msgstr "" msgid "Invalid aggregate function" msgstr "" -#: frappe/database/query.py:2414 +#: frappe/database/query.py:2415 msgid "Invalid alias format: {0}. Alias must be a simple identifier." msgstr "" @@ -13933,11 +13933,11 @@ msgstr "" msgid "Invalid app" msgstr "" -#: frappe/database/query.py:2374 frappe/database/query.py:2390 +#: frappe/database/query.py:2375 frappe/database/query.py:2391 msgid "Invalid argument format: {0}. Only quoted string literals or simple field names are allowed." msgstr "" -#: frappe/database/query.py:2338 +#: frappe/database/query.py:2339 msgid "Invalid argument type: {0}. Only strings, numbers, dicts, and None are allowed." msgstr "" @@ -13973,7 +13973,7 @@ msgstr "" msgid "Invalid expression set in filter {0} ({1})" msgstr "" -#: frappe/database/query.py:2141 +#: frappe/database/query.py:2142 msgid "Invalid field format for SELECT: {0}. Field names must be simple, backticked, table-qualified, aliased, or '*'." msgstr "" @@ -14009,7 +14009,7 @@ msgstr "" msgid "Invalid filter: {0}" msgstr "" -#: frappe/database/query.py:2258 +#: frappe/database/query.py:2259 msgid "Invalid function argument type: {0}. Only strings, numbers, lists, and None are allowed." msgstr "" @@ -14034,7 +14034,7 @@ msgstr "" msgid "Invalid naming series {}: dot (.) missing before the numeric placeholders. Kindly use a format like ABCD.#####." msgstr "" -#: frappe/database/query.py:2330 +#: frappe/database/query.py:2331 msgid "Invalid nested expression: dictionary must represent a function or operator" msgstr "" @@ -14100,7 +14100,7 @@ msgstr "" msgid "Invalid {0} condition" msgstr "" -#: frappe/database/query.py:2219 +#: frappe/database/query.py:2220 msgid "Invalid {0} dictionary format" msgstr "" @@ -14951,11 +14951,11 @@ msgid "Last Active" msgstr "" #: frappe/public/js/frappe/form/sidebar/form_sidebar.js:163 -msgid "Last Edited by You" +msgid "Last Edited By You" msgstr "" #: frappe/public/js/frappe/form/sidebar/form_sidebar.js:164 -msgid "Last Edited by {0}" +msgid "Last Edited By {0}" msgstr "" #. Label of the last_execution (Datetime) field in DocType 'Scheduled Job Type' @@ -15654,7 +15654,7 @@ msgstr "" #: frappe/public/js/frappe/list/base_list.js:509 #: frappe/public/js/frappe/list/list_view.js:405 #: frappe/public/js/frappe/ui/listing.html:16 -#: frappe/public/js/frappe/views/reports/query_report.js:1153 +#: frappe/public/js/frappe/views/reports/query_report.js:1154 msgid "Loading" msgstr "" @@ -16902,8 +16902,8 @@ msgid "Most probably your password is too long." msgstr "" #: frappe/core/doctype/communication/communication.js:86 -#: frappe/core/doctype/communication/communication.js:194 -#: frappe/core/doctype/communication/communication.js:212 +#: frappe/core/doctype/communication/communication.js:197 +#: frappe/core/doctype/communication/communication.js:215 #: frappe/public/js/frappe/form/grid_row_form.js:53 msgid "Move" msgstr "" @@ -17196,7 +17196,7 @@ msgstr "" msgid "Need Help?" msgstr "" -#: frappe/desk/doctype/workspace/workspace.py:360 +#: frappe/desk/doctype/workspace/workspace.py:363 msgid "Need Workspace Manager role to edit private workspace of other users" msgstr "" @@ -17448,7 +17448,7 @@ msgstr "" msgid "New {} releases for the following apps are available" msgstr "" -#: frappe/core/doctype/user/user.py:886 +#: frappe/core/doctype/user/user.py:890 msgid "Newly created user {0} has no roles enabled." msgstr "" @@ -17738,7 +17738,7 @@ msgstr "" msgid "No Results found" msgstr "" -#: frappe/core/doctype/user/user.py:887 +#: frappe/core/doctype/user/user.py:891 msgid "No Roles Specified" msgstr "" @@ -17778,7 +17778,7 @@ msgstr "" msgid "No changes in document" msgstr "" -#: frappe/public/js/frappe/views/workspace/workspace.js:762 +#: frappe/public/js/frappe/views/workspace/workspace.js:763 msgid "No changes made" msgstr "" @@ -17814,7 +17814,7 @@ msgstr "" msgid "No data to export" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1556 +#: frappe/public/js/frappe/views/reports/query_report.js:1557 msgid "No data to perform this action" msgstr "" @@ -18016,7 +18016,7 @@ msgstr "" msgid "Normalized Query" msgstr "" -#: frappe/core/doctype/user/user.py:1112 frappe/utils/oauth.py:301 +#: frappe/core/doctype/user/user.py:1116 frappe/utils/oauth.py:301 msgid "Not Allowed" msgstr "" @@ -18111,7 +18111,7 @@ msgstr "" msgid "Not a valid Comma Separated Value (CSV File)" msgstr "" -#: frappe/core/doctype/user/user.py:316 +#: frappe/core/doctype/user/user.py:320 msgid "Not a valid User Image." msgstr "" @@ -18985,7 +18985,7 @@ msgstr "" msgid "Operator must be one of {0}" msgstr "" -#: frappe/database/query.py:2286 +#: frappe/database/query.py:2287 msgid "Operator {0} requires exactly 2 arguments (left and right operands)" msgstr "" @@ -19188,7 +19188,7 @@ msgstr "" #: frappe/email/doctype/auto_email_report/auto_email_report.json #: frappe/printing/page/print/print.js:91 #: frappe/public/js/frappe/form/templates/print_layout.html:44 -#: frappe/public/js/frappe/views/reports/query_report.js:1948 +#: frappe/public/js/frappe/views/reports/query_report.js:1949 msgid "PDF" msgstr "" @@ -19563,7 +19563,7 @@ msgstr "" msgid "Password" msgstr "" -#: frappe/core/doctype/user/user.py:518 frappe/core/doctype/user/user.py:1188 +#: frappe/core/doctype/user/user.py:522 frappe/core/doctype/user/user.py:1192 msgid "Password Reset" msgstr "" @@ -19601,7 +19601,7 @@ msgstr "" msgid "Password not found for {0} {1} {2}" msgstr "" -#: frappe/core/doctype/user/user.py:1350 +#: frappe/core/doctype/user/user.py:1354 msgid "Password requirements not met" msgstr "" @@ -19613,7 +19613,7 @@ msgstr "" msgid "Password size exceeded the maximum allowed size" msgstr "" -#: frappe/core/doctype/user/user.py:959 +#: frappe/core/doctype/user/user.py:963 msgid "Password size exceeded the maximum allowed size." msgstr "" @@ -19782,7 +19782,8 @@ msgstr "" msgid "Permission" msgstr "" -#: frappe/database/query.py:993 frappe/desk/doctype/workspace/workspace.py:108 +#: frappe/database/query.py:993 frappe/database/query.py:1368 +#: frappe/desk/doctype/workspace/workspace.py:108 msgid "Permission Error" msgstr "" @@ -20030,11 +20031,11 @@ msgstr "" msgid "Please add a valid comment." msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1557 +#: frappe/public/js/frappe/views/reports/query_report.js:1558 msgid "Please adjust filters to include some data" msgstr "" -#: frappe/core/doctype/user/user.py:1159 +#: frappe/core/doctype/user/user.py:1163 msgid "Please ask your administrator to verify your sign-up" msgstr "" @@ -20062,7 +20063,7 @@ msgstr "" msgid "Please check the value of \"Fetch From\" set for field {0}" msgstr "" -#: frappe/core/doctype/user/user.py:1157 +#: frappe/core/doctype/user/user.py:1161 msgid "Please check your email for verification" msgstr "" @@ -20258,7 +20259,7 @@ msgstr "" msgid "Please select Minimum Password Score" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1246 +#: frappe/public/js/frappe/views/reports/query_report.js:1247 msgid "Please select X and Y fields" msgstr "" @@ -20320,7 +20321,7 @@ msgstr "" msgid "Please set a printer mapping for this print format in the Printer Settings" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1468 +#: frappe/public/js/frappe/views/reports/query_report.js:1469 msgid "Please set filters" msgstr "" @@ -20348,7 +20349,7 @@ msgstr "" msgid "Please setup a message first" msgstr "" -#: frappe/core/doctype/user/user.py:483 +#: frappe/core/doctype/user/user.py:487 msgid "Please setup default outgoing Email Account from Settings > Email Account" msgstr "" @@ -20708,7 +20709,7 @@ msgstr "" msgid "Primary Phone" msgstr "" -#: frappe/database/mariadb/schema.py:156 frappe/database/postgres/schema.py:202 +#: frappe/database/mariadb/schema.py:187 frappe/database/postgres/schema.py:273 #: frappe/database/sqlite/schema.py:141 msgid "Primary key of doctype {0} can not be changed as there are existing values." msgstr "" @@ -20726,7 +20727,7 @@ msgstr "" #: frappe/public/js/frappe/form/success_action.js:81 #: frappe/public/js/frappe/form/templates/print_layout.html:46 #: frappe/public/js/frappe/list/bulk_operations.js:95 -#: frappe/public/js/frappe/views/reports/query_report.js:1930 +#: frappe/public/js/frappe/views/reports/query_report.js:1931 #: frappe/public/js/frappe/views/reports/report_view.js:1630 #: frappe/public/js/frappe/views/treeview.js:501 frappe/www/printview.html:18 msgid "Print" @@ -20800,7 +20801,7 @@ msgstr "" msgid "Print Format Type" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1676 +#: frappe/public/js/frappe/views/reports/query_report.js:1677 msgid "Print Format not found" msgstr "" @@ -20980,7 +20981,7 @@ msgstr "" msgid "Proceed" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:973 +#: frappe/public/js/frappe/views/reports/query_report.js:974 msgid "Proceed Anyway" msgstr "" @@ -21505,7 +21506,7 @@ msgstr "" msgid "Re:" msgstr "" -#: frappe/core/doctype/communication/communication.js:268 +#: frappe/core/doctype/communication/communication.js:271 #: frappe/public/js/frappe/form/footer/form_timeline.js:601 #: frappe/public/js/frappe/views/communication.js:420 msgid "Re: {0}" @@ -21600,7 +21601,7 @@ msgstr "" msgid "Reason" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:927 +#: frappe/public/js/frappe/views/reports/query_report.js:928 msgid "Rebuild" msgstr "" @@ -21987,7 +21988,7 @@ msgstr "" #: frappe/public/js/frappe/form/form.js:1250 #: frappe/public/js/frappe/form/templates/print_layout.html:6 #: frappe/public/js/frappe/list/base_list.js:67 -#: frappe/public/js/frappe/views/reports/query_report.js:1919 +#: frappe/public/js/frappe/views/reports/query_report.js:1920 #: frappe/public/js/frappe/views/treeview.js:507 #: frappe/public/js/frappe/widgets/chart_widget.js:296 #: frappe/public/js/frappe/widgets/number_card_widget.js:358 @@ -22034,7 +22035,7 @@ msgstr "" msgid "Refreshing..." msgstr "" -#: frappe/core/doctype/user/user.py:1119 +#: frappe/core/doctype/user/user.py:1123 msgid "Registered but disabled" msgstr "" @@ -22198,7 +22199,7 @@ msgstr "" msgid "Removed" msgstr "" -#: frappe/desk/page/desktop/desktop.js:1197 +#: frappe/desk/page/desktop/desktop.js:1202 msgid "Removed Icons" msgstr "" @@ -22445,7 +22446,7 @@ msgstr "" #: frappe/core/report/prepared_report_analytics/prepared_report_analytics.py:39 #: frappe/desk/doctype/dashboard_chart/dashboard_chart.json #: frappe/desk/doctype/number_card/number_card.json -#: frappe/public/js/frappe/views/reports/query_report.js:2115 +#: frappe/public/js/frappe/views/reports/query_report.js:2116 msgid "Report Name" msgstr "" @@ -22497,7 +22498,7 @@ msgstr "" msgid "Report has no numeric fields, please change the Report Name" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1054 +#: frappe/public/js/frappe/views/reports/query_report.js:1055 msgid "Report initiated, click to view status" msgstr "" @@ -22517,7 +22518,7 @@ msgstr "" msgid "Report was not saved (there were errors)" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2153 +#: frappe/public/js/frappe/views/reports/query_report.js:2154 msgid "Report with more than 10 columns looks better in Landscape mode." msgstr "" @@ -22553,7 +22554,7 @@ msgstr "" msgid "Reports & Masters" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:970 +#: frappe/public/js/frappe/views/reports/query_report.js:971 msgid "Reports already in Queue" msgstr "" @@ -22655,7 +22656,7 @@ msgstr "" msgid "Requires any valid fdn path. i.e. ou=users,dc=example,dc=com" msgstr "" -#: frappe/core/doctype/communication/communication.js:279 +#: frappe/core/doctype/communication/communication.js:282 msgid "Res: {0}" msgstr "" @@ -23043,7 +23044,7 @@ msgstr "" msgid "Role and Level" msgstr "" -#: frappe/core/doctype/user/user.py:432 +#: frappe/core/doctype/user/user.py:436 msgid "Role has been set as per the user type {0}" msgstr "" @@ -23367,7 +23368,7 @@ msgstr "" msgid "SQL Queries" msgstr "" -#: frappe/database/query.py:2131 +#: frappe/database/query.py:2132 msgid "SQL functions are not allowed as strings in SELECT: {0}. Use dict syntax like {{'COUNT': '*'}} instead." msgstr "" @@ -23475,7 +23476,7 @@ msgstr "" #: frappe/public/js/frappe/views/kanban/kanban_settings.js:45 #: frappe/public/js/frappe/views/kanban/kanban_settings.js:189 #: frappe/public/js/frappe/views/kanban/kanban_view.js:358 -#: frappe/public/js/frappe/views/reports/query_report.js:2107 +#: frappe/public/js/frappe/views/reports/query_report.js:2108 #: frappe/public/js/frappe/views/reports/report_view.js:1837 #: frappe/public/js/frappe/views/workspace/workspace.js:384 #: frappe/public/js/frappe/widgets/base_widget.js:143 @@ -23498,7 +23499,7 @@ msgstr "" msgid "Save Customizations" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2110 +#: frappe/public/js/frappe/views/reports/query_report.js:2111 msgid "Save Report" msgstr "" @@ -23519,7 +23520,7 @@ msgstr "" #: frappe/printing/page/print_format_builder/print_format_builder.js:860 #: frappe/public/js/frappe/form/toolbar.js:315 #: frappe/public/js/frappe/views/kanban/kanban_board.bundle.js:928 -#: frappe/public/js/frappe/views/workspace/workspace.js:784 +#: frappe/public/js/frappe/views/workspace/workspace.js:785 msgid "Saved" msgstr "" @@ -23904,11 +23905,11 @@ msgstr "" msgid "Section must have at least one column" msgstr "" -#: frappe/core/doctype/user/user.py:1515 +#: frappe/core/doctype/user/user.py:1519 msgid "Security Alert: Your account is being impersonated" msgstr "" -#: frappe/core/doctype/user/user.py:402 +#: frappe/core/doctype/user/user.py:406 msgid "Security Alert: Your password has been changed." msgstr "" @@ -24725,7 +24726,7 @@ msgstr "" msgid "Set Filters for {0}" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2270 +#: frappe/public/js/frappe/views/reports/query_report.js:2271 msgid "Set Level" msgstr "" @@ -24943,7 +24944,7 @@ msgstr "" msgid "Setup > User Permissions" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1972 +#: frappe/public/js/frappe/views/reports/query_report.js:1973 #: frappe/public/js/frappe/views/reports/report_view.js:1815 msgid "Setup Auto Email" msgstr "" @@ -25416,7 +25417,7 @@ msgstr "" msgid "Sign Up and Confirmation" msgstr "" -#: frappe/core/doctype/user/user.py:1112 +#: frappe/core/doctype/user/user.py:1116 msgid "Sign Up is disabled" msgstr "" @@ -26480,11 +26481,11 @@ msgstr "" msgid "Successfully imported {0} out of {1} records." msgstr "" -#: frappe/desk/doctype/form_tour/form_tour.py:87 +#: frappe/desk/doctype/form_tour/form_tour.py:88 msgid "Successfully reset onboarding status for all users." msgstr "" -#: frappe/core/doctype/user/user.py:1534 +#: frappe/core/doctype/user/user.py:1538 msgid "Successfully signed out" msgstr "" @@ -26509,7 +26510,7 @@ msgstr "" msgid "Suggested Indexes" msgstr "" -#: frappe/core/doctype/user/user.py:804 +#: frappe/core/doctype/user/user.py:808 msgid "Suggested Username: {0}" msgstr "" @@ -27092,7 +27093,7 @@ msgstr "" msgid "Templates" msgstr "" -#: frappe/core/doctype/user/user.py:1125 +#: frappe/core/doctype/user/user.py:1129 msgid "Temporarily Disabled" msgstr "" @@ -27391,11 +27392,11 @@ msgstr "" msgid "The report you requested has been generated.

Click here to download:
{0}

This link will expire in {1} hours." msgstr "" -#: frappe/core/doctype/user/user.py:1083 +#: frappe/core/doctype/user/user.py:1087 msgid "The reset password link has been expired" msgstr "" -#: frappe/core/doctype/user/user.py:1085 +#: frappe/core/doctype/user/user.py:1089 msgid "The reset password link has either been used before or is invalid" msgstr "" @@ -27504,7 +27505,7 @@ msgstr "" msgid "There are no {0} for this {1}, why don't you start one!" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1006 +#: frappe/public/js/frappe/views/reports/query_report.js:1007 msgid "There are {0} with the same filters already in the queue:" msgstr "" @@ -27537,7 +27538,7 @@ msgstr "" msgid "There is some problem with the file url: {0}" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1003 +#: frappe/public/js/frappe/views/reports/query_report.js:1004 msgid "There is {0} with the same filters already in the queue:" msgstr "" @@ -27749,7 +27750,7 @@ msgstr "" msgid "This goes above the slideshow." msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2347 +#: frappe/public/js/frappe/views/reports/query_report.js:2348 msgid "This is a background report. Please set the appropriate filters and then generate a new one." msgstr "" @@ -27799,7 +27800,7 @@ msgstr "" msgid "This month" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1082 +#: frappe/public/js/frappe/views/reports/query_report.js:1083 msgid "This report contains {0} rows and is too big to display in browser, you can {1} this report instead." msgstr "" @@ -27807,7 +27808,7 @@ msgstr "" msgid "This report was generated on {0}" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:790 +#: frappe/public/js/frappe/views/reports/query_report.js:791 msgid "This report was generated {0}." msgstr "" @@ -27875,7 +27876,7 @@ msgstr "" msgid "This will terminate the job immediately and might be dangerous, are you sure?" msgstr "" -#: frappe/core/doctype/user/user.py:1365 +#: frappe/core/doctype/user/user.py:1369 msgid "Throttled" msgstr "" @@ -28340,7 +28341,7 @@ msgstr "" msgid "Too many requests. Please try again later." msgstr "" -#: frappe/core/doctype/user/user.py:1126 +#: frappe/core/doctype/user/user.py:1130 msgid "Too many users signed up recently, so the registration is disabled. Please try back in an hour" msgstr "" @@ -28403,7 +28404,7 @@ msgstr "" #: frappe/desk/query_report.py:756 #: frappe/public/js/frappe/views/reports/print_grid.html:50 -#: frappe/public/js/frappe/views/reports/query_report.js:1384 +#: frappe/public/js/frappe/views/reports/query_report.js:1385 #: frappe/public/js/frappe/views/reports/report_view.js:1645 msgid "Total" msgstr "" @@ -28556,7 +28557,7 @@ msgstr "" msgid "Translatable" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2408 +#: frappe/public/js/frappe/views/reports/query_report.js:2409 msgid "Translate Data" msgstr "" @@ -29074,7 +29075,7 @@ msgstr "" msgid "Unsupported function or operator: {0}" msgstr "" -#: frappe/database/query.py:2222 +#: frappe/database/query.py:2223 msgid "Unsupported {0}: {1}" msgstr "" @@ -29581,7 +29582,7 @@ msgstr "" #. Label of a Link in the Users Workspace #: frappe/core/page/permission_manager/permission_manager_help.html:97 #: frappe/core/workspace/users/users.json -#: frappe/public/js/frappe/views/reports/query_report.js:2094 +#: frappe/public/js/frappe/views/reports/query_report.js:2095 #: frappe/public/js/frappe/views/reports/report_view.js:1863 msgid "User Permissions" msgstr "" @@ -29683,15 +29684,15 @@ msgstr "" msgid "User with email: {0} does not exist in the system. Please ask 'System Administrator' to create the user for you." msgstr "" -#: frappe/core/doctype/user/user.py:609 +#: frappe/core/doctype/user/user.py:613 msgid "User {0} cannot be deleted" msgstr "" -#: frappe/core/doctype/user/user.py:378 +#: frappe/core/doctype/user/user.py:382 msgid "User {0} cannot be disabled" msgstr "" -#: frappe/core/doctype/user/user.py:682 +#: frappe/core/doctype/user/user.py:686 msgid "User {0} cannot be renamed" msgstr "" @@ -29712,11 +29713,11 @@ msgstr "" msgid "User {0} has requested for data deletion" msgstr "" -#: frappe/core/doctype/user/user.py:1509 +#: frappe/core/doctype/user/user.py:1513 msgid "User {0} has started an impersonation session as you.

Reason provided: {1}" msgstr "" -#: frappe/core/doctype/user/user.py:1492 +#: frappe/core/doctype/user/user.py:1496 msgid "User {0} impersonated as {1}" msgstr "" @@ -29745,7 +29746,7 @@ msgstr "" msgid "Username" msgstr "" -#: frappe/core/doctype/user/user.py:771 +#: frappe/core/doctype/user/user.py:775 msgid "Username {0} already exists" msgstr "" @@ -30041,6 +30042,10 @@ msgstr "" msgid "View Doctype Permissions" msgstr "" +#: frappe/public/js/frappe/form/info_card.js:48 +msgid "View Documentation" +msgstr "" + #: frappe/core/doctype/file/file.js:4 msgid "View File" msgstr "" @@ -30615,7 +30620,7 @@ msgstr "" msgid "Welcome Workspace" msgstr "" -#: frappe/core/doctype/user/user.py:475 +#: frappe/core/doctype/user/user.py:479 msgid "Welcome email sent" msgstr "" @@ -30623,11 +30628,11 @@ msgstr "" msgid "Welcome to Frappe!" msgstr "" -#: frappe/public/js/frappe/views/workspace/workspace.js:710 +#: frappe/public/js/frappe/views/workspace/workspace.js:711 msgid "Welcome to the {0} workspace" msgstr "" -#: frappe/core/doctype/user/user.py:542 +#: frappe/core/doctype/user/user.py:546 msgid "Welcome to {0}" msgstr "" @@ -30933,7 +30938,7 @@ msgstr "" msgid "Workspace added to desktop" msgstr "" -#: frappe/public/js/frappe/views/workspace/workspace.js:589 +#: frappe/public/js/frappe/views/workspace/workspace.js:590 msgid "Workspace {0} created" msgstr "" @@ -31000,7 +31005,7 @@ msgstr "" #. Label of the y_field (Select) field in DocType 'Dashboard Chart Field' #: frappe/desk/doctype/dashboard_chart_field/dashboard_chart_field.json -#: frappe/public/js/frappe/views/reports/query_report.js:1285 +#: frappe/public/js/frappe/views/reports/query_report.js:1286 msgid "Y Field" msgstr "" @@ -31692,7 +31697,7 @@ msgstr "" msgid "Your organization name and address for the email footer." msgstr "" -#: frappe/core/doctype/user/user.py:396 +#: frappe/core/doctype/user/user.py:400 msgid "Your password has been changed and you might have been logged out of all systems.
Please contact the Administrator for further assistance." msgstr "" @@ -32036,7 +32041,7 @@ msgstr "" msgid "just now" msgstr "" -#: frappe/desk/desktop.py:254 frappe/desk/query_report.py:301 +#: frappe/desk/desktop.py:251 frappe/desk/query_report.py:301 msgid "label" msgstr "" @@ -32552,7 +32557,7 @@ msgstr "" msgid "{0} Report" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:997 +#: frappe/public/js/frappe/views/reports/query_report.js:998 msgid "{0} Reports" msgstr "" @@ -32702,7 +32707,7 @@ msgstr "" msgid "{0} equals {1}" msgstr "" -#: frappe/database/mariadb/schema.py:141 frappe/database/postgres/schema.py:187 +#: frappe/database/mariadb/schema.py:172 frappe/database/postgres/schema.py:258 msgid "{0} field cannot be set as unique in {1}, as there are non-unique existing values" msgstr "" @@ -32966,7 +32971,7 @@ msgstr "" msgid "{0} items selected" msgstr "" -#: frappe/core/doctype/user/user.py:1501 +#: frappe/core/doctype/user/user.py:1505 msgid "{0} just impersonated as you. They gave this reason: {1}" msgstr "" @@ -33370,7 +33375,7 @@ msgstr "" msgid "{0}: {1} is set to state {2}" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:1343 +#: frappe/public/js/frappe/views/reports/query_report.js:1344 msgid "{0}: {1} vs {2}" msgstr "" From 6260bfe63c707ddf1c7642a7b9d523b6f5aadadb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 19:11:20 +0000 Subject: [PATCH 028/261] fix: timeline comment and email timestamps ignore show absolute datetime setting (#39307) (#39364) --- .../js/frappe/form/templates/timeline_message_box.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/templates/timeline_message_box.html b/frappe/public/js/frappe/form/templates/timeline_message_box.html index ebf8cc5cea5d..650ca40ddab5 100644 --- a/frappe/public/js/frappe/form/templates/timeline_message_box.html +++ b/frappe/public/js/frappe/form/templates/timeline_message_box.html @@ -1,5 +1,6 @@
{% is_mini = frappe.is_mobile() ? true : false %} + {% var show_absolute_datetime = cint(frappe.boot.user.show_absolute_datetime_in_timeline) || cint(frappe.boot.sysdefaults.show_absolute_datetime_in_timeline) %} {% if (doc.communication_type && doc.communication_type == "Automated Message") { %} @@ -24,7 +25,7 @@ {% } %}
- {{ comment_when(doc.communication_date || doc.creation) }} + {{ show_absolute_datetime ? frappe.datetime.str_to_user(doc.communication_date || doc.creation) : comment_when(doc.communication_date || doc.creation) }}
{% } else if (doc.comment_type && doc.comment_type == "Comment") { %} @@ -34,7 +35,7 @@ {{ __("commented") }} · - {{ comment_when(doc.communication_date || doc.creation, is_mini) }} + {{ show_absolute_datetime ? frappe.datetime.str_to_user(doc.communication_date || doc.creation) : comment_when(doc.communication_date || doc.creation, is_mini) }} {% if (doc.published) { %} · @@ -52,7 +53,7 @@ . - {{ comment_when(doc.communication_date || doc.creation, is_mini) }} + {{ show_absolute_datetime ? frappe.datetime.str_to_user(doc.communication_date || doc.creation) : comment_when(doc.communication_date || doc.creation, is_mini) }} {% if (doc.subject) { %}
{{doc.subject}}
From b3b807df77b15291e88db7adc7aff9724b7b9d49 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 18 May 2026 23:43:10 +0530 Subject: [PATCH 029/261] fix: support route_options for DocType links in sidebar When a sidebar item has `route_options` set directly (as a JSON string), use it to build the list URL instead of going through the `filters` path. This avoids the bug where `filters` with `["=", 1]` values get encoded as `%3D%2C1` in URLs on Frappe versions without `transform_filters`. (cherry picked from commit cad864552a4ec617038aa266f0a5a2307bae8d05) --- frappe/public/js/frappe/ui/sidebar/sidebar_item.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js index 77d9691e3188..c25dbb10d610 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js @@ -64,6 +64,9 @@ frappe.ui.sidebar_item.TypeLink = class SidebarItem { args.doc_view = "List"; args.route_options = filters_json; } + } else if (this.item.route_options && this.item.link_type == "DocType") { + args.doc_view = "List"; + args.route_options = JSON.parse(this.item.route_options); } path = frappe.utils.generate_route(args); } From 5ae004da6eaff878e3d25333f7348b687b744758 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 11:43:45 +0530 Subject: [PATCH 030/261] fix: support translated_doctypes in search_widget custom queries (backport #39366) (#39370) Co-authored-by: diptanilsaha Co-authored-by: Claude Sonnet 4.6 --- frappe/desk/search.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/frappe/desk/search.py b/frappe/desk/search.py index ac5a26c60270..83f853988691 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -110,15 +110,21 @@ def search_widget( filters = {} if query: # Query = custom search query i.e. python function + meta = frappe.get_meta(doctype) + # For translated doctypes, pass empty txt and a large page_length so the custom query + # returns all records without SQL-level text filtering; Python-level filtering against + # translated values is applied below. + query_txt = "" if meta.translated_doctype else txt + query_page_length = PAGE_LENGTH_FOR_LINK_VALIDATION if meta.translated_doctype else page_length try: is_whitelisted(frappe.get_attr(query)) - return frappe.call( + values = frappe.call( query, doctype, - txt, + query_txt, searchfield, start, - page_length, + query_page_length, filters, as_dict=as_dict, reference_doctype=reference_doctype, @@ -137,6 +143,14 @@ def search_widget( ) return [] + if not for_link_validation: + if meta.translated_doctype: + values = filter_translated(values, txt, as_dict) + values = sorted(values, key=lambda x: relevance_sorter(x, txt, as_dict)) + values = values[:page_length] + + return values + meta = frappe.get_meta(doctype) include_disabled = False @@ -225,15 +239,7 @@ def search_widget( if not for_link_validation: if meta.translated_doctype: - # Filtering the values array so that query is included in very element - values = ( - result - for result in values - if any( - re.search(f"{re.escape(txt)}.*", _(cstr(value)) or "", re.IGNORECASE) - for value in (result.values() if as_dict else result) - ) - ) + values = filter_translated(values, txt, as_dict) # Sorting the values array so that relevant results always come first # This will first bring elements on top in which query is a prefix of element @@ -382,6 +388,18 @@ def relevance_sorter(key, query, as_dict): return (cstr(value).casefold().startswith(query.casefold()) is not True, value) +def filter_translated(values, txt: str, as_dict: bool) -> list: + """Return only those results where txt matches any translated field value.""" + return [ + result + for result in values + if any( + re.search(f"{re.escape(txt)}.*", _(cstr(value)) or "", re.IGNORECASE) + for value in (result.values() if as_dict else result) + ) + ] + + @frappe.whitelist() def get_names_for_mentions(search_term): users_for_mentions = frappe.cache.get_value("users_for_mentions", get_users_for_mentions) From c1cc4cc4ef3ac2151fff330edd32aa96c13d4118 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Tue, 19 May 2026 11:20:01 +0530 Subject: [PATCH 031/261] fix(comments): override comment_by and comment_email Override these parsed params. to avoid spoofing. (cherry picked from commit 2d930e2d3747012cc8a8c8fbdccaf8c2c5070391) # Conflicts: # frappe/templates/includes/comments/comments.py --- frappe/templates/includes/comments/comments.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frappe/templates/includes/comments/comments.py b/frappe/templates/includes/comments/comments.py index 6d0dd8547721..0a9d03025093 100644 --- a/frappe/templates/includes/comments/comments.py +++ b/frappe/templates/includes/comments/comments.py @@ -25,7 +25,15 @@ def get_limit(): @frappe.whitelist(allow_guest=True) @rate_limit(limit=get_limit, seconds=60 * 60) +<<<<<<< HEAD def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route): +======= +def add_comment( + comment: str, comment_email: str, comment_by: str, reference_doctype: str, reference_name: str, route: str +): + comment_email = frappe.session.user + comment_by = frappe.get_value("User", frappe.session.user, "full_name") +>>>>>>> 2d930e2d37 (fix(comments): override comment_by and comment_email) if frappe.session.user == "Guest": allowed_doctypes = ["Web Page"] comments_permission_config = frappe.get_hooks("has_comment_permission") From 21e8479f432a6bd1452e82e9286ff49ff207d4a6 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Tue, 19 May 2026 16:09:35 +0530 Subject: [PATCH 032/261] chore: resolve conflicts --- frappe/templates/includes/comments/comments.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frappe/templates/includes/comments/comments.py b/frappe/templates/includes/comments/comments.py index 0a9d03025093..6a6d54f683bb 100644 --- a/frappe/templates/includes/comments/comments.py +++ b/frappe/templates/includes/comments/comments.py @@ -25,15 +25,11 @@ def get_limit(): @frappe.whitelist(allow_guest=True) @rate_limit(limit=get_limit, seconds=60 * 60) -<<<<<<< HEAD -def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route): -======= def add_comment( comment: str, comment_email: str, comment_by: str, reference_doctype: str, reference_name: str, route: str ): comment_email = frappe.session.user comment_by = frappe.get_value("User", frappe.session.user, "full_name") ->>>>>>> 2d930e2d37 (fix(comments): override comment_by and comment_email) if frappe.session.user == "Guest": allowed_doctypes = ["Web Page"] comments_permission_config = frappe.get_hooks("has_comment_permission") From a5af756ae03aa32ae6da7520a27a5e3eeaac05c4 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Wed, 13 May 2026 17:40:56 +0530 Subject: [PATCH 033/261] fix: update field index mapping to use .get() (cherry picked from commit c352abed9f0a6be8b7ef7f7a06bdbf942e0caf0d) --- frappe/utils/xlsxutils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/utils/xlsxutils.py b/frappe/utils/xlsxutils.py index 2be53f0eb8e6..5ea476ee643f 100644 --- a/frappe/utils/xlsxutils.py +++ b/frappe/utils/xlsxutils.py @@ -109,8 +109,9 @@ def __init__(self, metadata: XLSXMetadata, default_styling: bool = True): self.metadata = metadata # column fieldname -> index mapping + # !NOTE: use .get() — columns may be DocField objects self.field_index_map = { - col["fieldname"]: idx for idx, col in self.metadata.column_map.items() if col.get("fieldname") + col.get("fieldname"): idx for idx, col in self.metadata.column_map.items() if col.get("fieldname") } self.styles: list[dict] = [] From 7c15d6fe71ba4a48eb164f0bd414c89c61f0aabc Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Wed, 13 May 2026 19:06:05 +0530 Subject: [PATCH 034/261] fix: enhance report column building with default field metadata (cherry picked from commit 7b2e97a74324b057a6cc93a3b422e039c2dfa790) --- frappe/core/doctype/report/report.py | 55 +++++++++++++++++++++------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index cbc3f2ece9ea..1bcb1412651a 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -370,28 +370,55 @@ def get_standard_report_order_by(self, params): return order_by, group_by, group_by_args def build_standard_report_columns(self, columns, group_by_args): - _columns = [] + from frappe.model.meta import get_default_df + + report_columns = [] for fieldname, doctype in columns: meta = frappe.get_meta(doctype) - if meta.get_field(fieldname): - field = meta.get_field(fieldname) + meta_df = meta.get_field(fieldname) + df = meta_df or get_default_df(fieldname) + + if df: + column = frappe._dict( + { + "fieldname": df.fieldname, + "label": _(df.label or "") if meta_df else meta.get_label(df.fieldname), + "fieldtype": df.fieldtype, + "options": df.options, + "translatable": df.translatable or False, + "hidden": df.hidden or False, + } + ) + + if ( + column.fieldtype == "Link" + and column.options + and frappe.get_meta(column.options).translated_doctype + ): + column.translatable = True else: - if fieldname == "_aggregate_column": - label = get_group_by_column_label(group_by_args, meta) - else: - label = meta.get_label(fieldname) + label = ( + get_group_by_column_label(group_by_args, meta) + if fieldname == "_aggregate_column" + else meta.get_label(fieldname) + ) - field = frappe._dict(fieldname=fieldname, label=label) + column = frappe._dict( + { + "fieldname": fieldname, + "label": label, + "fieldtype": "Link" if fieldname == "name" else "Data", + "options": doctype if fieldname == "name" else None, + "translatable": False, + "hidden": False, + } + ) - # since name is the primary key for a document, it will always be a Link datatype - if fieldname == "name": - field.fieldtype = "Link" - field.options = doctype + report_columns.append(column) - _columns.append(field) - return _columns + return report_columns def build_data_dict(self, result, columns): data = [] From be82e6dd4203122f9adf971756b3c603498226bd Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Tue, 19 May 2026 16:14:22 +0530 Subject: [PATCH 035/261] fix: streamline column metadata retrieval in report generation (cherry picked from commit 5d1564cba5568e08a4a7de0a6297931d28619298) --- frappe/core/doctype/report/report.py | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 1bcb1412651a..40d771fd64b0 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -377,27 +377,13 @@ def build_standard_report_columns(self, columns, group_by_args): for fieldname, doctype in columns: meta = frappe.get_meta(doctype) - meta_df = meta.get_field(fieldname) - df = meta_df or get_default_df(fieldname) + if meta_df := meta.get_field(fieldname): + column = meta_df.as_dict() + elif default_df := get_default_df(fieldname): + column = default_df.copy() - if df: - column = frappe._dict( - { - "fieldname": df.fieldname, - "label": _(df.label or "") if meta_df else meta.get_label(df.fieldname), - "fieldtype": df.fieldtype, - "options": df.options, - "translatable": df.translatable or False, - "hidden": df.hidden or False, - } - ) - - if ( - column.fieldtype == "Link" - and column.options - and frappe.get_meta(column.options).translated_doctype - ): - column.translatable = True + if not column.get("label"): + column.label = meta.get_label(fieldname) else: label = ( get_group_by_column_label(group_by_args, meta) @@ -411,8 +397,6 @@ def build_standard_report_columns(self, columns, group_by_args): "label": label, "fieldtype": "Link" if fieldname == "name" else "Data", "options": doctype if fieldname == "name" else None, - "translatable": False, - "hidden": False, } ) From 411a5d547de670727b52d4c25eb7a24a0791e328 Mon Sep 17 00:00:00 2001 From: sokumon Date: Mon, 18 May 2026 17:56:42 +0530 Subject: [PATCH 036/261] fix: remove chat bubble from trial site (cherry picked from commit 540738c122b4f958ddea9907a2e3bde9fa3fa819) --- frappe/public/js/billing.bundle.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/billing.bundle.js b/frappe/public/js/billing.bundle.js index 824f1c12e195..86a13fc8b806 100644 --- a/frappe/public/js/billing.bundle.js +++ b/frappe/public/js/billing.bundle.js @@ -1,12 +1,12 @@ let frappeCloudBaseEndpoint = "https://frappecloud.com"; -let isFCUser = false; +let isFCUser = true; $(document).ready(function () { const site_info = frappe.boot.site_info; if (site_info) { const trial_end_date = new Date(site_info.trial_end_date); frappeCloudBaseEndpoint = site_info.base_url; - isFCUser = site_info.is_fc_user; + // isFCUser = site_info.is_fc_user; const today = new Date(); const diffTime = trial_end_date - today; @@ -33,7 +33,8 @@ $(document).ready(function () { !frappe.is_mobile() && frappe.user.has_role("System Manager"); if (visiblity_condition && isFCUser) { - if (site_info.trial_end_date && trial_end_date > new Date()) { + let chat_bubble_visiblity = false; + if (chat_bubble_visiblity && site_info.trial_end_date && trial_end_date > new Date()) { addChatBubble(); toggleChatBubble(true); } From 40e314ae9bc7ae4e166ff985175efd41f6a8f524 Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 19 May 2026 18:18:02 +0530 Subject: [PATCH 037/261] fix: move onboarding panel to the right (cherry picked from commit ca157ab076e7a86fd549c26fd36474d6f7e5bdae) --- .../ui/user_onboarding/user_onboarding.bundle.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/ui/user_onboarding/user_onboarding.bundle.js b/frappe/public/js/frappe/ui/user_onboarding/user_onboarding.bundle.js index 53dbd0f19a06..cea1051e3777 100644 --- a/frappe/public/js/frappe/ui/user_onboarding/user_onboarding.bundle.js +++ b/frappe/public/js/frappe/ui/user_onboarding/user_onboarding.bundle.js @@ -72,7 +72,7 @@ function addStyles() { .onb-panel { position: fixed; - left: 66px; + right: 20px; bottom: 24px; width: 310px; max-height: 80vh; @@ -89,20 +89,11 @@ function addStyles() { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } - .expanded .onb-panel { - left: 236px; - } - .onb-collapsible { overflow: hidden; transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1); } - .onb-collapsible--expanded { - max-height: 3000px; - pointer-events: auto; - } - .onb-collapsible--collapsed { max-height: 0; pointer-events: none; From 31393db04f7343c9eb59f4579f45a6b001f526ef Mon Sep 17 00:00:00 2001 From: KerollesFathy Date: Mon, 18 May 2026 12:47:17 +0000 Subject: [PATCH 038/261] fix: remove `padding-block` from select to fix descender clipping in Firefox (cherry picked from commit 0f64e62c9310bf694b74f67967f033382eb9e2af) --- frappe/public/scss/common/controls.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/scss/common/controls.scss b/frappe/public/scss/common/controls.scss index b4db5b80363f..371f06ac73f6 100644 --- a/frappe/public/scss/common/controls.scss +++ b/frappe/public/scss/common/controls.scss @@ -49,7 +49,6 @@ select.form-control { -webkit-appearance: none; -moz-appearance: none; appearance: none; - padding-block: 0; } /* table multiselect */ From 6a59c562a04fea572d82d6aec29945021232e5e4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 00:55:50 +0530 Subject: [PATCH 039/261] fix: don't close context menu when clicking items inside it (backport #39365) (#39390) Co-authored-by: Nabin Hait --- frappe/public/js/frappe/ui/menu.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/menu.js b/frappe/public/js/frappe/ui/menu.js index aae005ce72ef..e59ea2578c38 100644 --- a/frappe/public/js/frappe/ui/menu.js +++ b/frappe/public/js/frappe/ui/menu.js @@ -303,8 +303,11 @@ frappe.ui.create_menu = function (opts) { document.addEventListener( "click", - function () { - if (frappe.menu_map[context_menu.name].visible) { + function (e) { + if ( + frappe.menu_map[context_menu.name].visible && + !context_menu.template[0].contains(e.target) + ) { frappe.menu_map[context_menu.name].hide(); opts.onHide && opts.onHide(opts.parent); } From 230b1413a446609202724136033c98c841bb612d Mon Sep 17 00:00:00 2001 From: Kerolles Fathy Date: Wed, 20 May 2026 06:08:03 +0300 Subject: [PATCH 040/261] fix: show impersonate button based on permission instead of hardcoded Administrator check (#39387) - replace hardcoded `frappe.session.user === "Administrator"` with `frm.has_perm("impersonate")` to respect the new impersonate permission type - keep Administrator bypassing the permission check as the top authority - prevent self-impersonation for all users by comparing `frm.doc.name !== frappe.session.user` (cherry picked from commit 3d7b819e5f1c6984badbea1ed7d47e76ac856cef) --- frappe/core/doctype/user/user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 7e40e3ccd46e..72bd294f0e66 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -374,8 +374,8 @@ frappe.ui.form.on("User", { }, setup_impersonation: function (frm) { if ( - frappe.session.user === "Administrator" && - frm.doc.name != "Administrator" && + (frappe.session.user === "Administrator" || frm.has_perm("impersonate")) && + frm.doc.name !== frappe.session.user && !frm.is_new() ) { frm.add_custom_button(__("Impersonate"), () => { From 02f90ebac1078e20aeb6580f9fe6f7f4115ededb Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Thu, 16 Apr 2026 13:57:49 +0530 Subject: [PATCH 041/261] fix(page): improve secure local resource access (cherry picked from commit 0c3cef5237391079fc8dd2f84941de12603a75d1) --- frappe/utils/pdf_generator/page.py | 34 +++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/frappe/utils/pdf_generator/page.py b/frappe/utils/pdf_generator/page.py index 634ded049d8e..78513f2ddc93 100644 --- a/frappe/utils/pdf_generator/page.py +++ b/frappe/utils/pdf_generator/page.py @@ -116,8 +116,15 @@ def intercept_and_fulfill(): def intercept_request_for_local_resources(self, url_pattern="*"): """Starts intercepting network requests for the given target_id and URL pattern.""" + import os + data = {} + bench_sites = os.path.abspath(os.path.join(frappe.utils.get_bench_path(), "sites")) + asset_path = os.path.abspath(os.path.join(bench_sites, "assets")) + site_public_root = os.path.realpath(frappe.utils.get_site_path("public")) + files_path = os.path.realpath(frappe.utils.get_site_path("public", "files")) + def on_request_paused_event(future, response): """Callback for when a request is paused (intercepted).""" params = response.get("params") @@ -127,11 +134,17 @@ def on_request_paused_event(future, response): if url.startswith(get_host_url()): path = url.replace(get_host_url(), "").split("?v", 1)[0] - if path.startswith("assets/") or path.startswith("files/"): - path = urllib.parse.unquote(path) - if path.startswith("files/"): - path = frappe.utils.get_site_path("public", path) - content = frappe.read_file(path, as_base64=True) + clean_path = urllib.parse.unquote(path) + + if clean_path.startswith("assets/"): + final_system_path = os.path.abspath(os.path.join(bench_sites, clean_path)) + is_safe = os.path.commonpath([final_system_path, asset_path]) == asset_path + else: + final_system_path = os.path.realpath(os.path.join(site_public_root, clean_path)) + is_safe = os.path.commonpath([final_system_path, files_path]) == files_path + + if is_safe: + content = frappe.read_file(final_system_path, as_base64=True) response_headers = [] # write logic to handle all file types as required if path.endswith(".svg"): @@ -148,6 +161,17 @@ def on_request_paused_event(future, response): return_future=True, ) return + elif path: + self.session.send( + "Fetch.failRequest", + {"requestId": data["request_id"], "errorReason": "AccessDenied"}, + return_future=True, + ) + frappe.log_error( + title="Attempted Unauthorized File Access in PDF Generator", + message=f"Blocked access to: {path} \nResolved Path to: {final_system_path}", + ) + return self.session.send( "Fetch.continueRequest", {"requestId": data["request_id"]}, From 69be97cf314e53e314678c5af98fef25f5c2d7e6 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 20 May 2026 06:06:15 +0000 Subject: [PATCH 042/261] chore(release): Bumped to Version 16.18.3 ## [16.18.3](https://github.com/frappe/frappe/compare/v16.18.2...v16.18.3) (2026-05-20) ### Bug Fixes * cast read-only field to string on update after submit ([75c2fde](https://github.com/frappe/frappe/commit/75c2fde85c314c5f0537f612bc1dc79c73fcaf0d)) * child table link fields break for guest submissions ([14b5d72](https://github.com/frappe/frappe/commit/14b5d72a92babbb6882c4396082e0111ba27ff3e)) * **comments:** override comment_by and comment_email ([c1cc4cc](https://github.com/frappe/frappe/commit/c1cc4cc4ef3ac2151fff330edd32aa96c13d4118)) * delete only orphan workspaces ([74c16c3](https://github.com/frappe/frappe/commit/74c16c3d4571c72535818147213cbce39956086b)) * document follow unfollow broken due to PEP 563 lazy annotations bypassing type coercion ([c8a89da](https://github.com/frappe/frappe/commit/c8a89da12b95c20ef0cfe316d36ac47eb2ef9e00)) * don't close context menu when clicking items inside it (backport [#39365](https://github.com/frappe/frappe/issues/39365)) ([#39390](https://github.com/frappe/frappe/issues/39390)) ([6a59c56](https://github.com/frappe/frappe/commit/6a59c562a04fea572d82d6aec29945021232e5e4)) * enhance report column building with default field metadata ([7c15d6f](https://github.com/frappe/frappe/commit/7c15d6fe71ba4a48eb164f0bd414c89c61f0aabc)) * guard against empty image_field in get_preview_data ([#39275](https://github.com/frappe/frappe/issues/39275)) ([475408b](https://github.com/frappe/frappe/commit/475408b04f5871f20c4a657a92c2652ca771fa03)) * handle total row csv conversion ([0824b11](https://github.com/frappe/frappe/commit/0824b1104f17457246330c11a64751072812ea6c)) * hide Customize button for core DocTypes ([9bb3c40](https://github.com/frappe/frappe/commit/9bb3c40b5b5eb8a4f1d83990a8b7f82d1c93040e)) * **login:** disallow header override when generating temp. login link ([8caa55c](https://github.com/frappe/frappe/commit/8caa55cfbe60e13d2cf90990ec64c80817b8d1cc)) * move onboarding panel to the right ([40e314a](https://github.com/frappe/frappe/commit/40e314ae9bc7ae4e166ff985175efd41f6a8f524)) * only check for valid child table fields to compare doctypes ([3ae7230](https://github.com/frappe/frappe/commit/3ae72309b013118649b50189803565dbed0607a6)) * **page:** improve secure local resource access ([02f90eb](https://github.com/frappe/frappe/commit/02f90ebac1078e20aeb6580f9fe6f7f4115ededb)) * perm check indentation in web form ([a29c93b](https://github.com/frappe/frappe/commit/a29c93baef10106db8762d12a7bac99cd00981e2)) * prevent duplicate settings button in session defaults dialog ([4327f6d](https://github.com/frappe/frappe/commit/4327f6dd2a52c5ddc278d482e37b2db85a662dc6)) * prevent report message from being overwritten by stale report message ([79176f8](https://github.com/frappe/frappe/commit/79176f8c19db06537a24d9c3939acbf6b842a9b9)) * remove `padding-block` from select to fix descender clipping in Firefox ([31393db](https://github.com/frappe/frappe/commit/31393db04f7343c9eb59f4579f45a6b001f526ef)) * remove chat bubble from trial site ([411a5d5](https://github.com/frappe/frappe/commit/411a5d547de670727b52d4c25eb7a24a0791e328)) * remove orphan entites before creating new assets ([df1cbd9](https://github.com/frappe/frappe/commit/df1cbd96bb00a272e9044197d0602b216757be6f)) * **safe_exec:** block queries that export records ([493a2d4](https://github.com/frappe/frappe/commit/493a2d47497ff5b54fecc1975734b9fb85accf29)) * skip self-follow on User doc in add_docshare ([68343cb](https://github.com/frappe/frappe/commit/68343cb2e6dc5e751e36beb9529f755acc98910f)) * skip standard report validation in migrate ([aecbc10](https://github.com/frappe/frappe/commit/aecbc10b5a2e338dfcca149f4665fa7777f1def6)) * streamline column metadata retrieval in report generation ([be82e6d](https://github.com/frappe/frappe/commit/be82e6dd4203122f9adf971756b3c603498226bd)) * support route_options for DocType links in sidebar ([b3b807d](https://github.com/frappe/frappe/commit/b3b807df77b15291e88db7adc7aff9724b7b9d49)) * support translated_doctypes in search_widget custom queries (backport [#39366](https://github.com/frappe/frappe/issues/39366)) ([#39370](https://github.com/frappe/frappe/issues/39370)) ([5ae004d](https://github.com/frappe/frappe/commit/5ae004da6eaff878e3d25333f7348b687b744758)) * timeline comment and email timestamps ignore show absolute datetime setting ([#39307](https://github.com/frappe/frappe/issues/39307)) ([#39364](https://github.com/frappe/frappe/issues/39364)) ([6260bfe](https://github.com/frappe/frappe/commit/6260bfe63c707ddf1c7642a7b9d523b6f5aadadb)) * toggle report message when filters change ([55f4b50](https://github.com/frappe/frappe/commit/55f4b50fb816d2db61e10beef261a12eccf77daa)) * update field index mapping to use .get() ([a5af756](https://github.com/frappe/frappe/commit/a5af756ae03aa32ae6da7520a27a5e3eeaac05c4)) * use autocomplete for showing child table links in web form ([e1fd139](https://github.com/frappe/frappe/commit/e1fd139e9a110561354248bfeae0634fe99e4183)) * workspace editing condition ([345016b](https://github.com/frappe/frappe/commit/345016b08a4610be28eaf5ac581d791b406b651f)) ### Reverts * changes to dead path in file uploader ([#39248](https://github.com/frappe/frappe/issues/39248)) ([#39329](https://github.com/frappe/frappe/issues/39329)) ([d828b8a](https://github.com/frappe/frappe/commit/d828b8a4929ef0d5c63fb91c15df56610e2784c0)), closes [#33995](https://github.com/frappe/frappe/issues/33995) --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index fd0d2d59907f..79864b8eea7f 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -55,7 +55,7 @@ render_template, ) -__version__ = "16.18.2" +__version__ = "16.18.3" __title__ = "Frappe Framework" if TYPE_CHECKING: # pragma: no cover From 91eea3a69be51f4cac129c04f04ff2c5ef334ee2 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Sun, 26 Apr 2026 01:53:05 +0530 Subject: [PATCH 043/261] fix: navigation breaks when sort field has null values (cherry picked from commit c9a783aa1390308ccc5b9ec2e9cad9229f1b1402) --- frappe/desk/form/utils.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index c0c665ed486c..0c1c4640f40d 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -10,6 +10,7 @@ from frappe import _ from frappe.core.doctype.file.utils import extract_images_from_html from frappe.desk.form.document_follow import follow_document +from frappe.query_builder.functions import IfNull if TYPE_CHECKING: from frappe.core.doctype.comment.comment import Comment @@ -92,9 +93,12 @@ def get_next( filters = json.loads(filters) table = frappe.qb.DocType(doctype) - sort_column = table[sort_field] name_column = table.name + fallback = _sort_field_fallback(doctype, sort_field) + sort_column = IfNull(table[sort_field], fallback) current_sort_value = frappe.db.get_value(doctype, value, sort_field) + if current_sort_value is None: + current_sort_value = fallback is_ascending = sort_order.lower() == "asc" if prev == is_ascending: @@ -123,5 +127,17 @@ def get_next( return None +def _sort_field_fallback(doctype: str, fieldname: str): + df = frappe.get_meta(doctype).get_field(fieldname) + if df: + if df.fieldtype in ("Float", "Int", "Currency", "Percent", "Check"): + return 0 + if df.fieldtype in ("Date", "Datetime"): + return "0001-01-01" + if df.fieldtype == "Time": + return "00:00:00" + return "" + + def get_pdf_link(doctype, docname, print_format="Standard", no_letterhead=0): return f"/api/method/frappe.utils.print_format.download_pdf?doctype={doctype}&name={docname}&format={print_format}&no_letterhead={no_letterhead}" From a831821aea7bf18cfe22c282fb1ebe3727c63a4b Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Sun, 26 Apr 2026 02:13:34 +0530 Subject: [PATCH 044/261] test: cover null sort field handling in get_next (cherry picked from commit 73e5a9cd1485bfe7c246e29985913c9bf0af91be) --- frappe/desk/form/test_form.py | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/frappe/desk/form/test_form.py b/frappe/desk/form/test_form.py index 1c7a8f011446..3526d5b7750b 100644 --- a/frappe/desk/form/test_form.py +++ b/frappe/desk/form/test_form.py @@ -3,6 +3,7 @@ import frappe from frappe.desk.form.linked_with import get_linked_docs, get_linked_doctypes +from frappe.desk.form.utils import _sort_field_fallback, get_next from frappe.tests import IntegrationTestCase @@ -11,3 +12,50 @@ def test_linked_with(self): results = get_linked_docs("Role", "System Manager", linkinfo=get_linked_doctypes("Role")) self.assertTrue("User" in results) self.assertTrue("DocType" in results) + + def test_sort_field_fallback(self): + self.assertEqual(_sort_field_fallback("Note", "public"), 0) + self.assertEqual(_sort_field_fallback("Note", "expire_notification_on"), "0001-01-01") + self.assertEqual(_sort_field_fallback("Event Notifications", "time"), "00:00:00") + self.assertEqual(_sort_field_fallback("Note", "title"), "") + self.assertEqual(_sort_field_fallback("Note", "nonexistent_field_xyz"), "") + + def test_get_next_with_null_sort_field(self): + notes = [] + for i, expire in enumerate([None, "2099-01-01", None]): + note = frappe.get_doc( + { + "doctype": "Note", + "title": f"test_navigate_null_{frappe.generate_hash(length=8)}_{i}", + "expire_notification_on": expire, + } + ).insert(ignore_permissions=True) + notes.append(note.name) + + try: + filters = {"name": ["in", notes]} + + next_name = get_next( + "Note", + notes[0], + prev=0, + sort_field="expire_notification_on", + sort_order="asc", + filters=filters, + ) + self.assertIsNotNone(next_name) + self.assertIn(next_name, notes) + + prev_name = get_next( + "Note", + notes[1], + prev=1, + sort_field="expire_notification_on", + sort_order="asc", + filters=filters, + ) + self.assertIsNotNone(prev_name) + self.assertIn(prev_name, [notes[0], notes[2]]) + finally: + for name in notes: + frappe.delete_doc("Note", name, force=1, ignore_permissions=True) From 50b88dab8a2244f13de9ebb993e815da0022c0f0 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Sun, 26 Apr 2026 02:35:42 +0530 Subject: [PATCH 045/261] perf: skip ifnull wrap when sort field is non-nullable (cherry picked from commit c3b0508821d1184ef3cb79d5fcf7ae9f17ae2f0d) --- frappe/desk/form/test_form.py | 4 +++- frappe/desk/form/utils.py | 30 +++++++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/frappe/desk/form/test_form.py b/frappe/desk/form/test_form.py index 3526d5b7750b..ed695625b4fe 100644 --- a/frappe/desk/form/test_form.py +++ b/frappe/desk/form/test_form.py @@ -14,7 +14,9 @@ def test_linked_with(self): self.assertTrue("DocType" in results) def test_sort_field_fallback(self): - self.assertEqual(_sort_field_fallback("Note", "public"), 0) + self.assertIsNone(_sort_field_fallback("Note", "name")) + self.assertIsNone(_sort_field_fallback("Note", "creation")) + self.assertIsNone(_sort_field_fallback("Note", "public")) self.assertEqual(_sort_field_fallback("Note", "expire_notification_on"), "0001-01-01") self.assertEqual(_sort_field_fallback("Event Notifications", "time"), "00:00:00") self.assertEqual(_sort_field_fallback("Note", "title"), "") diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index 0c1c4640f40d..665cc33086dc 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -94,11 +94,14 @@ def get_next( table = frappe.qb.DocType(doctype) name_column = table.name - fallback = _sort_field_fallback(doctype, sort_field) - sort_column = IfNull(table[sort_field], fallback) current_sort_value = frappe.db.get_value(doctype, value, sort_field) - if current_sort_value is None: - current_sort_value = fallback + fallback = _sort_field_fallback(doctype, sort_field) + if fallback is not None: + sort_column = IfNull(table[sort_field], fallback) + if current_sort_value is None: + current_sort_value = fallback + else: + sort_column = table[sort_field] is_ascending = sort_order.lower() == "asc" if prev == is_ascending: @@ -128,14 +131,19 @@ def get_next( def _sort_field_fallback(doctype: str, fieldname: str): + if fieldname in ("name", "modified", "creation", "modified_by", "owner", "idx", "docstatus"): + return None df = frappe.get_meta(doctype).get_field(fieldname) - if df: - if df.fieldtype in ("Float", "Int", "Currency", "Percent", "Check"): - return 0 - if df.fieldtype in ("Date", "Datetime"): - return "0001-01-01" - if df.fieldtype == "Time": - return "00:00:00" + if df is None: + return "" + if df.fieldtype in ("Check", "Float", "Int", "Currency", "Percent"): + return None + if getattr(df, "not_nullable", False): + return None + if df.fieldtype in ("Date", "Datetime"): + return "0001-01-01" + if df.fieldtype == "Time": + return "00:00:00" return "" From 188e51ad7af0e30d1f6894758d59995decebcff3 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Tue, 19 May 2026 12:20:39 +0530 Subject: [PATCH 046/261] fix(reportview): add perm. check to save_report Endpoint lacked perms. so added perm. check for report creation & ref. doctype (read). (cherry picked from commit 1f7d36f5def2076bccda9bab3302cab69639b86f) --- frappe/desk/reportview.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 3aa27f8d56ef..c46b70038fbb 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -333,6 +333,13 @@ def save_report(name, doctype, report_settings): if report.owner != frappe.session.user and not report.has_permission("write"): frappe.throw(_("Insufficient Permissions for editing Report"), frappe.PermissionError) else: + if not frappe.has_permission("Report", "create"): + frappe.throw(_("You don't have permission to create Report records."), frappe.PermissionError) + if not frappe.has_permission(doctype, "read"): + frappe.throw( + _("You don't have permission to create report for {0}").format(_(doctype)), + frappe.PermissionError, + ) report = frappe.new_doc("Report") report.report_name = name report.ref_doctype = doctype From dc99290c1a44a876e9f0478a36d3b78103b2e6c3 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Mon, 20 Apr 2026 18:34:08 +0530 Subject: [PATCH 047/261] feat(utils): add util to ensure sandboxing This util can be used in places where sandboxing is needed. (cherry picked from commit 7c9ce26469d4beb1e9d407c87c7d22fa7df38116) # Conflicts: # frappe/core/doctype/file/utils.py --- frappe/core/doctype/file/utils.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frappe/core/doctype/file/utils.py b/frappe/core/doctype/file/utils.py index a4952e710c7f..7a338ecc0003 100644 --- a/frappe/core/doctype/file/utils.py +++ b/frappe/core/doctype/file/utils.py @@ -463,3 +463,23 @@ def find_file_by_url(path: str, name: str | None = None) -> "File" | None: file: File = frappe.get_doc(doctype="File", **file_data) if file.is_downloadable(): return file +<<<<<<< HEAD +======= + + +def get_safe_file_name(file_name: str) -> str: + return re.sub(r"[/\\%?#]", "_", file_name) + + +def check_path_safety(base_path: str, requested_path: str) -> bool: + """Util to check path safety by ensuring sandboxing and logging unsuccessful attempts""" + base_path = os.path.realpath(base_path) + requested_path = os.path.realpath(requested_path) + if os.path.commonpath([base_path, requested_path]) != base_path: + frappe.log_error( + title="Attempted Unauthorized File Access", + message=f"Blocked access to: {requested_path}", + ) + return False + return True +>>>>>>> 7c9ce26469 (feat(utils): add util to ensure sandboxing) From 4e1afbf9e953e3ac6bac7e0b49204857212621f9 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Mon, 20 Apr 2026 18:57:46 +0530 Subject: [PATCH 048/261] fix(response): harden download_backup Made use of util `check_path_safety` to ensure sandboxing. (cherry picked from commit 0c660477ee567f458a2ab0a68b56bbd4c8135faa) --- frappe/utils/response.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frappe/utils/response.py b/frappe/utils/response.py index ba4a6f797986..03b4fff4705f 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -26,6 +26,7 @@ import frappe.utils from frappe import _ from frappe.core.doctype.access_log.access_log import make_access_log +from frappe.core.doctype.file.utils import check_path_safety from frappe.utils import format_timedelta, orjson_dumps if TYPE_CHECKING: @@ -280,6 +281,13 @@ def download_backup(path): _("You need to be logged in and have System Manager Role to be able to access backups.") ) + filename = path.split("/backups/", 1)[1] + backup_path = frappe.get_site_path("private", "backups") + requested_path = frappe.get_site_path("private", "backups", filename) + is_safe = check_path_safety(base_path=backup_path, requested_path=requested_path) + if not is_safe: + frappe.throw(_("Invalid backup path"), frappe.PermissionError) + return send_private_file(path) From 05ef831567f3c36fe9c67bee252270937d2fc45c Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 31 Mar 2026 12:42:56 +0530 Subject: [PATCH 049/261] feat: store last sidebar shown (cherry picked from commit ed8f8766f10ab50e20f378eafa114596cc1e04c3) --- frappe/public/js/frappe/ui/sidebar/sidebar.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.js b/frappe/public/js/frappe/ui/sidebar/sidebar.js index 3c8a24430e5f..c904710bf1f0 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.js @@ -276,6 +276,7 @@ frappe.ui.Sidebar = class Sidebar { this.setup_onboarding(); }); + this.store_last_show_sidebar_for_item(); } add_card(card) { if (this.cards && this.cards.find((i) => i.title === card.title)) return; @@ -298,6 +299,7 @@ frappe.ui.Sidebar = class Sidebar { } setup_events() { const me = this; + this.setup_reload(); frappe.router.on("change", function (router) { if (frappe.route_options.sidebar) { frappe.app.sidebar.setup(frappe.route_options.sidebar); @@ -632,6 +634,7 @@ frappe.ui.Sidebar = class Sidebar { try { let route = frappe.get_route(); let view, entity_name; + let sidebar_item_map = JSON.parse(localStorage.getItem("sidebar_item_map")); switch (route.length) { case 1: view = "Page"; @@ -663,6 +666,14 @@ frappe.ui.Sidebar = class Sidebar { this.set_active_workspace_item(); return; } + if (sidebar_item_map[entity_name]) { + this.setup(sidebar_item_map[entity_name][0]); + return; + } + if (this.sidebar_title && sidebars.includes(this.sidebar_title)) { + this.set_active_workspace_item(); + return; + } if (module) { sidebars = this.filter_sidebars_from_app( sidebars, @@ -752,4 +763,22 @@ frappe.ui.Sidebar = class Sidebar { }); return sidebars; } + setup_reload() { + const me = this; + this.item_sidebar_map = {}; + $(window).on("beforeunload", function () { + me.store_last_show_sidebar_for_item(); + }); + } + store_last_show_sidebar_for_item() { + const me = this; + if (frappe.app.sidebar.active_item) { + let active_item = frappe.app.sidebar.active_item.parent().data("id"); + if (!me.item_sidebar_map[active_item]) { + me.item_sidebar_map[active_item] = []; + } + me.item_sidebar_map[active_item].push(me.sidebar_title); + localStorage.setItem("sidebar_item_map", JSON.stringify(me.item_sidebar_map)); + } + } }; From 881329028b89a7bde9480c4f2583d7d670873c7f Mon Sep 17 00:00:00 2001 From: sokumon Date: Wed, 20 May 2026 19:56:32 +0530 Subject: [PATCH 050/261] fix: add padding block (cherry picked from commit 653aec4e8d5a0fcd80bbb14c2c45cfabf53c4b7e) --- frappe/public/scss/common/controls.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/scss/common/controls.scss b/frappe/public/scss/common/controls.scss index 371f06ac73f6..78c5feb75b7a 100644 --- a/frappe/public/scss/common/controls.scss +++ b/frappe/public/scss/common/controls.scss @@ -49,6 +49,7 @@ select.form-control { -webkit-appearance: none; -moz-appearance: none; appearance: none; + padding-block: 0px; } /* table multiselect */ From 40a5def83cdd992c958452c0af52605c779846c2 Mon Sep 17 00:00:00 2001 From: Gajendra Nishad <75714258+gajjug004@users.noreply.github.com> Date: Thu, 21 May 2026 12:02:09 +0530 Subject: [PATCH 051/261] fix(multi_select_dialog): apply array setter default on initial search (#39419) (cherry picked from commit 007274fd7babfc3f41c9f98535ad6b580638926c) --- frappe/public/js/frappe/form/multi_select_dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index bef75dae906d..78e211b0307a 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -562,7 +562,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { if ($.isArray(this.setters)) { for (let df of this.setters) { filters[df.fieldname] = - me.dialog.fields_dict[df.fieldname].get_value() || undefined; + me.dialog.fields_dict[df.fieldname].get_value() || df.default || undefined; me.args[df.fieldname] = filters[df.fieldname]; filter_fields.push(df.fieldname); } From b7f6baa0eaa2ca7aff8b5219b8097ca5e163b04e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 12:05:54 +0530 Subject: [PATCH 052/261] chore: add exclude-newer option for uv tool in pyproject.toml (#39415) (#39420) (cherry picked from commit 8e129e5fd7f8a730e5ab23998ed96a29884cdb13) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4ae556ae3118..12db3c20f599 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -214,6 +214,9 @@ nixpkgs-deps = [ "python312", ] +[tool.uv] +exclude-newer = "7 days" + [tool.mypy] strict = false pretty = true From 465fccf59995236a0164bcffc65e2e37a2a5f097 Mon Sep 17 00:00:00 2001 From: sokumon Date: Thu, 21 May 2026 15:30:36 +0530 Subject: [PATCH 053/261] fix: handle export when child table is empty (cherry picked from commit c7afab39065426e0bcd93fb6dae93d7527449985) --- frappe/core/doctype/data_import/exporter.py | 4 ++-- frappe/utils/__init__.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/data_import/exporter.py b/frappe/core/doctype/data_import/exporter.py index 4cb63651e3e3..67a82807e279 100644 --- a/frappe/core/doctype/data_import/exporter.py +++ b/frappe/core/doctype/data_import/exporter.py @@ -124,14 +124,14 @@ def get_data_to_export(self): raise frappe.PermissionError( _("You are not allowed to export {} doctype").format(self.doctype) ) - for doc in data: rows = [] rows = self.add_data_row(self.doctype, None, doc, rows, 0) if table_fields: # add child table data for f in table_fields: - for i, child_row in enumerate(doc.get(f, [])): + table_data = doc.get(f, []) or [] + for i, child_row in enumerate(table_data): table_df = self.meta.get_field(f) child_doctype = table_df.options rows = self.add_data_row(child_doctype, child_row.parentfield, child_row, rows, i) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index bd3a1e0f6f97..6f9508a49406 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -1038,8 +1038,9 @@ def groupby_metric(iterable: dict[str, list], key: str): """ records = {} for category, items in iterable.items(): - for item in items: - records.setdefault(item[key], {}).setdefault(category, []).append(item) + if items: + for item in items: + records.setdefault(item[key], {}).setdefault(category, []).append(item) return records From d1ed98dc423082d5303528faa167427336d36638 Mon Sep 17 00:00:00 2001 From: sokumon Date: Wed, 14 Jan 2026 13:45:53 +0530 Subject: [PATCH 054/261] fix: close notifications correctly (cherry picked from commit ea3b6a04a35413af463be87836d76d9f9b813592) # Conflicts: # frappe/public/js/frappe/ui/notifications/notifications.js --- .../js/frappe/ui/notifications/notifications.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index 3c4b6957e0e9..b58953a5737b 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -4,8 +4,16 @@ frappe.ui.Notifications = class Notifications { constructor(opts) { this.tabs = {}; this.notification_settings = frappe.boot.notification_settings; +<<<<<<< HEAD this.full_height = opts?.full_height || true; this.full_height = opts?.full_height || false; +======= + if (!opts?.full_height) { + this.full_height = true; + } + this.full_height = opts?.full_height; + +>>>>>>> ea3b6a04a3 (fix: close notifications correctly) this.wrapper = opts?.wrapper || $(".standard-items-sections"); this.make(); } @@ -53,8 +61,10 @@ frappe.ui.Notifications = class Notifications { ${frappe.utils.icon("x")}
`) .on("click", (e) => { - if (!this.full_height) { + if (this.full_height) { this.dropdown.addClass("hidden"); + } else { + this.dropdown_list.addClass("hidden"); } }) .appendTo(this.header_actions); From d3b14442a7a8b498eca3a42530ccafdfbbc66f68 Mon Sep 17 00:00:00 2001 From: Soham Kulkarni <77533095+sokumon@users.noreply.github.com> Date: Thu, 21 May 2026 19:31:08 +0530 Subject: [PATCH 055/261] chore: merge conflicts --- frappe/public/js/frappe/ui/notifications/notifications.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index b58953a5737b..a8031ca3ce14 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -4,16 +4,8 @@ frappe.ui.Notifications = class Notifications { constructor(opts) { this.tabs = {}; this.notification_settings = frappe.boot.notification_settings; -<<<<<<< HEAD this.full_height = opts?.full_height || true; - this.full_height = opts?.full_height || false; -======= - if (!opts?.full_height) { - this.full_height = true; - } - this.full_height = opts?.full_height; ->>>>>>> ea3b6a04a3 (fix: close notifications correctly) this.wrapper = opts?.wrapper || $(".standard-items-sections"); this.make(); } From 80d0ac77b8de780d6c972583d0c42f431ac7f63e Mon Sep 17 00:00:00 2001 From: Soham Kulkarni <77533095+sokumon@users.noreply.github.com> Date: Thu, 21 May 2026 19:32:30 +0530 Subject: [PATCH 056/261] chore: merge conflicts --- frappe/public/js/frappe/ui/notifications/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index a8031ca3ce14..80b545a3eb3d 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -4,7 +4,7 @@ frappe.ui.Notifications = class Notifications { constructor(opts) { this.tabs = {}; this.notification_settings = frappe.boot.notification_settings; - this.full_height = opts?.full_height || true; + this.full_height = opts?.full_height || false; this.wrapper = opts?.wrapper || $(".standard-items-sections"); this.make(); From d3b1867efbed84f2882af0730368f98e7ae623b8 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Thu, 21 May 2026 12:13:06 +0530 Subject: [PATCH 057/261] fix: don't keep stale columns in custom report after filter change (cherry picked from commit 643a785c55b3d9826c295f3544328e3274e2a850) --- frappe/desk/query_report.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index c3396915f0fb..6027ee61b5cc 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -99,8 +99,13 @@ def generate_report_result( result = normalize_result(result, columns) if report.get("custom_columns"): - # saved columns (with custom columns / with different column order) - columns = report.custom_columns + # keep saved columns still returned by this run, plus user-added + # custom columns (`link_field`); drops columns stale after a filter change + columns = [ + column + for column in report.custom_columns + if column.get("link_field") or column["fieldname"] in report_column_names + ] # unsaved custom_columns if custom_columns: From f2a60c2eaedbf405fb944b6e9e27f2a5aa37e3cd Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 16:01:15 +0000 Subject: [PATCH 058/261] fix(form-sidebar): escape `title` and `frm.doc.name` (backport #39444) (#39445) Co-authored-by: diptanilsaha --- frappe/public/js/frappe/form/templates/form_sidebar.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 5fdd5176b4c5..9886638ad948 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -32,7 +32,7 @@
{% let title = frm.get_title(); %} -
+
{%= frappe.utils.escape_html(frappe.utils.html2text(title)) %}
{% if frm.meta.beta %} @@ -41,8 +41,8 @@
{% endif %} {% if (title && title !== frm.doc.name) { %} -
- {%= frm.doc.name %} +
+ {%= frappe.utils.escape_html(frappe.utils.html2text(frm.doc.name)) %}
{% } %}
From af42a90443cb384df74928d5b96478b3244e5b92 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Fri, 22 May 2026 16:38:19 +0530 Subject: [PATCH 059/261] chore: resolve conflicts --- frappe/core/doctype/file/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/frappe/core/doctype/file/utils.py b/frappe/core/doctype/file/utils.py index 7a338ecc0003..3f038c3df45f 100644 --- a/frappe/core/doctype/file/utils.py +++ b/frappe/core/doctype/file/utils.py @@ -463,8 +463,6 @@ def find_file_by_url(path: str, name: str | None = None) -> "File" | None: file: File = frappe.get_doc(doctype="File", **file_data) if file.is_downloadable(): return file -<<<<<<< HEAD -======= def get_safe_file_name(file_name: str) -> str: @@ -482,4 +480,3 @@ def check_path_safety(base_path: str, requested_path: str) -> bool: ) return False return True ->>>>>>> 7c9ce26469 (feat(utils): add util to ensure sandboxing) From aea7f704fbd5d154ee69a02bb9a39a020fd256ec Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Tue, 19 May 2026 16:29:29 +0530 Subject: [PATCH 060/261] fix(deleted_document): restrict restoration of docs. to sys. man. Restoring deleted documents should be a higher privilege action, restricting this to system manager. (cherry picked from commit f723c23f96161d63da8bcabb2b2670aa52b97f39) # Conflicts: # frappe/core/doctype/deleted_document/deleted_document.py --- .../core/doctype/deleted_document/deleted_document.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index ef4578f9c975..309aa46cde3c 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -38,7 +38,12 @@ def clear_old_logs(days=180): @frappe.whitelist() +<<<<<<< HEAD def restore(name, alert=True): +======= +def restore(name: str | int, alert: bool = True): + frappe.only_for("System Manager") +>>>>>>> f723c23f96 (fix(deleted_document): restrict restoration of docs. to sys. man.) deleted = frappe.get_doc("Deleted Document", name) if deleted.restored: @@ -69,7 +74,12 @@ def restore(name, alert=True): @frappe.whitelist() +<<<<<<< HEAD def bulk_restore(docnames): +======= +def bulk_restore(docnames: str | list[str]): + frappe.only_for("System Manager") +>>>>>>> f723c23f96 (fix(deleted_document): restrict restoration of docs. to sys. man.) docnames = frappe.parse_json(docnames) message = _("Restoring Deleted Document") restored, invalid, failed = [], [], [] From d5c5499c95953b0bb28f7b4907add01663bb8ca0 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Tue, 19 May 2026 16:29:29 +0530 Subject: [PATCH 061/261] fix(deleted_document): retain original metadata on restore Ideally metadata like owner, creation etc... should be retained even after doc. deletion. This was previously being overwritten likely due to insert, so this fixes that by retaining original metadata. (cherry picked from commit 1867022ac35616cd32e1533f07913f8991c481ba) --- .../deleted_document/deleted_document.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index 309aa46cde3c..3e228fde6819 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -50,6 +50,10 @@ def restore(name: str | int, alert: bool = True): frappe.throw(_("Document {0} Already Restored").format(name), exc=frappe.DocumentAlreadyRestored) doc = frappe.get_doc(json.loads(deleted.data)) + original_owner = doc.get("owner") + original_creation = doc.get("creation") + original_modified = doc.get("modified") + original_modified_by = doc.get("modified_by") doc.flags.from_restore = True try: doc.insert() @@ -63,6 +67,19 @@ def restore(name: str | int, alert: bool = True): doc.set(workflow_state_fieldname, None) doc.insert() + # retain original metadata + frappe.db.set_value( + doc.doctype, + doc.name, + { + "owner": original_owner, + "creation": original_creation, + "modified": original_modified, + "modified_by": original_modified_by, + }, + update_modified=False, + ) + doc.add_comment("Edit", _("restored {0} as {1}").format(deleted.deleted_name, doc.name)) deleted.new_name = doc.name From 793e545d61b36b89862bca2a910fada33712a1c4 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Tue, 19 May 2026 18:40:52 +0530 Subject: [PATCH 062/261] fix: add perm. checks Added perm. checks (create & read) (cherry picked from commit a8bfd044e4db1c882086ac297e5f67569bb9f6a4) --- .../core/doctype/deleted_document/deleted_document.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index 3e228fde6819..b1fa3a3bc4ba 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -50,6 +50,16 @@ def restore(name: str | int, alert: bool = True): frappe.throw(_("Document {0} Already Restored").format(name), exc=frappe.DocumentAlreadyRestored) doc = frappe.get_doc(json.loads(deleted.data)) + + if not frappe.has_permission(doc.doctype, "create"): + frappe.throw( + _("You do not have permission to create or restore documents of type {0}.").format(doc.doctype), + frappe.PermissionError, + ) + + if not frappe.has_permission(doc.doctype, "read", doc=doc): + frappe.throw(_("You do not have permission to restore this document."), frappe.PermissionError) + original_owner = doc.get("owner") original_creation = doc.get("creation") original_modified = doc.get("modified") From 3a8d741b4cd202d44c0402ea01fb40266fa86e81 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Wed, 20 May 2026 09:35:28 +0530 Subject: [PATCH 063/261] test: add test for metadata retention (cherry picked from commit 62be07a161da39354ab3612c16018eac5231e415) --- .../deleted_document/test_deleted_document.py | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/deleted_document/test_deleted_document.py b/frappe/core/doctype/deleted_document/test_deleted_document.py index 522d6aa996a5..102166aac8fd 100644 --- a/frappe/core/doctype/deleted_document/test_deleted_document.py +++ b/frappe/core/doctype/deleted_document/test_deleted_document.py @@ -1,7 +1,30 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # License: MIT. See LICENSE +import frappe +from frappe.core.doctype.deleted_document.deleted_document import restore from frappe.tests import IntegrationTestCase class TestDeletedDocument(IntegrationTestCase): - pass + def test_metadata_retention(self): + frappe.set_user("Administrator") + doc = frappe.get_doc({"doctype": "Note", "title": "Test Note", "content": "Test Content"}).insert() + orig_owner = doc.owner + orig_creation = doc.creation + orig_modified = doc.modified + orig_modified_by = doc.modified_by + + frappe.delete_doc("Note", doc.name, force=True) + self.assertFalse(frappe.db.exists("Note", doc.name)) + + log_name = frappe.db.get_value("Deleted Document", {"deleted_name": doc.name, "restored": 0}) + restore(log_name, alert=False) + + new_restored_name = frappe.db.get_value("Deleted Document", log_name, "new_name") + + restored_doc = frappe.get_doc("Note", new_restored_name) + + self.assertEqual(restored_doc.owner, orig_owner) + self.assertEqual(str(restored_doc.creation), str(orig_creation)) + self.assertEqual(str(restored_doc.modified), str(orig_modified)) + self.assertEqual(restored_doc.modified_by, orig_modified_by) From a2e7ba4815ee81c6471c1e6dbadfc40777dca3f3 Mon Sep 17 00:00:00 2001 From: sokumon Date: Fri, 22 May 2026 14:40:19 +0530 Subject: [PATCH 064/261] fix: populate link title in timeline_links (cherry picked from commit dba66ff813019440767db678e1ccff160920e907) --- frappe/core/doctype/communication/communication.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 16dae174ec0f..450853044054 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -447,11 +447,19 @@ def deduplicate_timeline_links(self): self.add_link(doctype, name) def add_link(self, link_doctype, link_name, autosave=False): + title_field = frappe.get_meta(link_doctype).get_title_field() + link_title = ( + frappe.db.get_value(link_doctype, link_name, title_field, cache=True, order_by=None) + if title_field != "name" + else None + ) + self.append( "timeline_links", { "link_doctype": link_doctype, "link_name": link_name, + "link_title": link_title or link_name, "communication_date": self.communication_date, }, ) From f5355c2c035587d4f35404d75584b42f88b60156 Mon Sep 17 00:00:00 2001 From: gajjug004 Date: Tue, 21 Apr 2026 15:47:25 +0530 Subject: [PATCH 065/261] fix(grid): include cross-page selections in get_selected() (cherry picked from commit b2aeb42fd09b52a01dba83bcc7d8d7f6eae08568) --- frappe/public/js/frappe/form/grid.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 897af1459d6f..3f80d3af433a 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -390,13 +390,7 @@ export default class Grid { ); get_selected() { - return (this.grid_rows || []) - .map((row) => { - return row.doc.__checked ? row.doc.name : null; - }) - .filter((d) => { - return d; - }); + return (this.data || []).filter((doc) => doc.__checked).map((doc) => doc.name); } get_selected_children() { From ec13e20b870929840fd718c156cd6f0f23b0eb30 Mon Sep 17 00:00:00 2001 From: gajjug004 Date: Sun, 26 Apr 2026 21:23:10 +0530 Subject: [PATCH 066/261] feat(grid): show banner for selected rows (cherry picked from commit 8e83af26262547cfc8283fdfcc3ccb986f8002d7) --- frappe/public/js/frappe/form/grid.js | 17 +++++++++++++++++ frappe/public/scss/common/grid.scss | 21 +++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 3f80d3af433a..2c07bc703d34 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -243,9 +243,26 @@ export default class Grid { this.refresh_remove_rows_button(); this.refresh_duplicate_rows_button(); + this.update_selection_banner(num_selected_rows); }); } + update_selection_banner(count) { + let $container = this.wrapper.find(".form-grid-container"); + let $toast = this.wrapper.find("> .grid-selection-toast"); + if (count > 0) { + if (!$toast.length) { + $toast = $( + `
` + ).insertAfter($container); + } + $toast.find(".grid-selection-toast__message").text(__("{0} rows selected", [count])); + $toast.show(); + } else if ($toast.length) { + $toast.hide(); + } + } + /** * Checks or unchecks all checkboxes between two rows (included), given their docnames. * Rows are only checked only if both parameters are valid docnames. diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index d77fb4f9b71e..5e1a2a46d62c 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -720,6 +720,27 @@ width: 100%; } +.grid-selection-toast { + position: sticky; + bottom: var(--padding-md); + width: fit-content; + margin-left: auto; + margin-right: auto; + margin-top: calc(var(--padding-xl) * -1); + z-index: 2; + pointer-events: none; + display: none; + + &__message { + display: inline-block; + background-color: rgba(0, 0, 0, 0.8); + color: var(--bg-color); + border-radius: var(--border-radius-md); + padding: var(--padding-xs) var(--padding-sm); + font-size: var(--text-sm); + } +} + .form-grid-container:has(.grid-row.grid-row-open) { overflow-x: clip; white-space: normal; From 3dfdc6303149cb49e24d84db577a1f86016382d6 Mon Sep 17 00:00:00 2001 From: gajjug004 Date: Sat, 2 May 2026 17:19:27 +0530 Subject: [PATCH 067/261] chore(grid): tweak selection banner css (cherry picked from commit 159db3d884c0f95cb660180af4de3e099da549f2) --- frappe/public/scss/common/grid.scss | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index 5e1a2a46d62c..92875d048b28 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -728,16 +728,14 @@ margin-right: auto; margin-top: calc(var(--padding-xl) * -1); z-index: 2; - pointer-events: none; display: none; &__message { display: inline-block; background-color: rgba(0, 0, 0, 0.8); color: var(--bg-color); - border-radius: var(--border-radius-md); - padding: var(--padding-xs) var(--padding-sm); - font-size: var(--text-sm); + border-radius: var(--border-radius-sm); + padding: var(--padding-xs) var(--padding-md); } } From 06d47796542fab68cff3266cd76ca2ece9869ae8 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Sat, 23 May 2026 21:59:06 +0530 Subject: [PATCH 068/261] fix: rebuild chromium singleton when subprocess has exited (cherry picked from commit 2e9a6ea729bf49ccfa45d5e474a9bfdea4fa657f) --- frappe/utils/pdf_generator/chrome_pdf_generator.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frappe/utils/pdf_generator/chrome_pdf_generator.py b/frappe/utils/pdf_generator/chrome_pdf_generator.py index 41736f7934f4..32561469fcde 100644 --- a/frappe/utils/pdf_generator/chrome_pdf_generator.py +++ b/frappe/utils/pdf_generator/chrome_pdf_generator.py @@ -27,8 +27,14 @@ def remove_browser(self, browser): self._browsers.remove(browser) def __new__(cls): - # if instance or _chromium_process is not available create object else return current instance stored in cls._instance - if cls._instance is None or not cls._instance._chromium_process: + # Rebuild singleton when chromium subprocess is missing or has exited. + # subprocess.Popen stays truthy after the underlying process dies, so + # `not cls._instance._chromium_process` never trips — use poll() instead. + if ( + cls._instance is None + or cls._instance._chromium_process is None + or cls._instance._chromium_process.poll() is not None + ): cls._instance = super().__new__(cls) return cls._instance From 5a30ad2366457ee969488d244e3d30ba3dfd2fad Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Sat, 23 May 2026 21:33:24 +0530 Subject: [PATCH 069/261] fix: release chromium browser when pdf generation raises (cherry picked from commit d533ac40bfc02294a11bf7e4bd0dfdf110c70b8b) --- frappe/utils/pdf.py | 13 ++- frappe/utils/pdf_generator/browser.py | 85 ++++++++++--------- .../pdf_generator/chrome_pdf_generator.py | 3 +- 3 files changed, 56 insertions(+), 45 deletions(-) diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 1f733a0e504c..630173d6f6e7 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -176,10 +176,15 @@ def get_chrome_pdf(print_format, html, options, output, pdf_generator=None): # scrubbing url to expand url is not required as we have set url. # also, planning to remove network requests anyway 🤞 generator = ChromePDFGenerator() - browser = Browser(generator, print_format, html, options) - transformer = PDFTransformer(browser) - # transforms and merges header, footer into body pdf and returns merged pdf - return transformer.transform_pdf(output=output) + browser = None + try: + browser = Browser(generator, print_format, html, options) + transformer = PDFTransformer(browser) + # transforms and merges header, footer into body pdf and returns merged pdf + return transformer.transform_pdf(output=output) + finally: + if browser is not None: + generator.remove_browser(browser.browserID) def get_file_data_from_writer(writer_obj): diff --git a/frappe/utils/pdf_generator/browser.py b/frappe/utils/pdf_generator/browser.py index 93a137af2d8d..7d9b2c4d8b45 100644 --- a/frappe/utils/pdf_generator/browser.py +++ b/frappe/utils/pdf_generator/browser.py @@ -14,49 +14,54 @@ def __init__(self, generator, print_format, html, options): self.debug_mode = frappe.conf.developer_mode and bool(frappe.form_dict.get("pdf_debug")) self.browserID = frappe.utils.random_string(10) generator.add_browser(self.browserID) - # sets soup from html - self.set_html(html) - # sets wkhtmltopdf options - self.set_options(options) - # start cdp connection and create browser context ( kind of like new window / incognito mode) - self.open(generator) - # opens header and footer pages and sets content ( not waiting for it to load) - self.prepare_header_footer() - # opens body page and sets content and waits for it to finshing load - self.setup_body_page() - # prepare options as per chrome for pdf - self.prepare_options_for_pdf() - # generate header and footer pages if they are not dynamic ( first, odd, even, last) - self.update_header_footer_page_pd() - # if header and footer are not dynamic start generating pdf for them (non-blocking) - self.try_async_header_footer_pdf() - # now wait for page to load as we need DOM to generate pdf - self.body_page.wait_for_set_content() - self.body_pdf = self.body_page.generate_pdf(raw=not self.header_page and not self.footer_page) - if not self.debug_mode: - self.body_page.close() - self.update_header_footer_page() - - if self.header_page: - if not self.is_header_dynamic: - self.header_pdf = self.header_page.get_pdf_from_stream(self.header_page.get_pdf_stream_id()) - else: - self.header_pdf = self.header_page.generate_pdf() + try: + # sets soup from html + self.set_html(html) + # sets wkhtmltopdf options + self.set_options(options) + # start cdp connection and create browser context ( kind of like new window / incognito mode) + self.open(generator) + # opens header and footer pages and sets content ( not waiting for it to load) + self.prepare_header_footer() + # opens body page and sets content and waits for it to finshing load + self.setup_body_page() + # prepare options as per chrome for pdf + self.prepare_options_for_pdf() + # generate header and footer pages if they are not dynamic ( first, odd, even, last) + self.update_header_footer_page_pd() + # if header and footer are not dynamic start generating pdf for them (non-blocking) + self.try_async_header_footer_pdf() + # now wait for page to load as we need DOM to generate pdf + self.body_page.wait_for_set_content() + self.body_pdf = self.body_page.generate_pdf(raw=not self.header_page and not self.footer_page) if not self.debug_mode: - self.header_page.close() + self.body_page.close() + self.update_header_footer_page() + + if self.header_page: + if not self.is_header_dynamic: + self.header_pdf = self.header_page.get_pdf_from_stream( + self.header_page.get_pdf_stream_id() + ) + else: + self.header_pdf = self.header_page.generate_pdf() + if not self.debug_mode: + self.header_page.close() + + if self.footer_page: + if not self.is_footer_dynamic: + self.footer_pdf = self.footer_page.get_pdf_from_stream( + self.footer_page.get_pdf_stream_id() + ) + else: + self.footer_pdf = self.footer_page.generate_pdf() + if not self.debug_mode: + self.footer_page.close() - if self.footer_page: - if not self.is_footer_dynamic: - self.footer_pdf = self.footer_page.get_pdf_from_stream(self.footer_page.get_pdf_stream_id()) - else: - self.footer_pdf = self.footer_page.generate_pdf() if not self.debug_mode: - self.footer_page.close() - - if not self.debug_mode: - self.close() - - generator.remove_browser(self.browserID) + self.close() + finally: + generator.remove_browser(self.browserID) if self.debug_mode: generator.detach_debug_browser() diff --git a/frappe/utils/pdf_generator/chrome_pdf_generator.py b/frappe/utils/pdf_generator/chrome_pdf_generator.py index 32561469fcde..f7cc5badc691 100644 --- a/frappe/utils/pdf_generator/chrome_pdf_generator.py +++ b/frappe/utils/pdf_generator/chrome_pdf_generator.py @@ -24,7 +24,8 @@ def add_browser(self, browser): self._browsers.append(browser) def remove_browser(self, browser): - self._browsers.remove(browser) + if browser in self._browsers: + self._browsers.remove(browser) def __new__(cls): # Rebuild singleton when chromium subprocess is missing or has exited. From 56164a9a0fda652aa880307e75241da05889f0ea Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Thu, 21 May 2026 12:39:21 +0530 Subject: [PATCH 070/261] fix: restore object-format dynamic filters in dashboard charts --- .../public/js/frappe/utils/dashboard_utils.js | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/frappe/public/js/frappe/utils/dashboard_utils.js b/frappe/public/js/frappe/utils/dashboard_utils.js index 46059db89e27..b8cb88c13862 100644 --- a/frappe/public/js/frappe/utils/dashboard_utils.js +++ b/frappe/public/js/frappe/utils/dashboard_utils.js @@ -207,29 +207,42 @@ frappe.dashboard_utils = { ? JSON.parse(doc.dynamic_filters_json) : null; - if (!dynamic_filters || !Object.keys(dynamic_filters).length) { + const has_dynamic_filters = Array.isArray(dynamic_filters) + ? dynamic_filters.length + : dynamic_filters && Object.keys(dynamic_filters).length; + + if (!has_dynamic_filters) { return filters; } - if (Array.isArray(dynamic_filters)) { - dynamic_filters.forEach((f) => { + if (!Array.isArray(dynamic_filters)) { + Object.keys(dynamic_filters).forEach((key) => { try { - f[3] = eval(f[3]); + dynamic_filters[key] = eval(dynamic_filters[key]); } catch (e) { - frappe.throw(__("Invalid expression set in filter {0} ({1})", [f[1], f[0]])); + frappe.throw(__("Invalid expression set in filter {0}", [key])); } }); + + return filters ? Object.assign(filters, dynamic_filters) : dynamic_filters; + } + + dynamic_filters.forEach((f) => { + try { + f[3] = eval(f[3]); + } catch (e) { + frappe.throw(__("Invalid expression set in filter {0} ({1})", [f[1], f[0]])); + } + }); + + if (!filters) { + filters = dynamic_filters; + } else if (Array.isArray(filters)) { filters = [...filters, ...dynamic_filters]; } else { - for (let key of Object.keys(dynamic_filters)) { - try { - const val = eval(dynamic_filters[key]); - dynamic_filters[key] = val; - } catch (e) { - frappe.throw(__("Invalid expression set in filter {0}", [key])); - } - } - Object.assign(filters, dynamic_filters); + dynamic_filters.forEach((f) => { + filters[f[1]] = f[3]; + }); } return filters; From 763e6c4c27a07ab2072d32c116b7ca1a611ac790 Mon Sep 17 00:00:00 2001 From: sokumon Date: Thu, 15 Jan 2026 17:27:14 +0530 Subject: [PATCH 071/261] fix: show module sidebar (cherry picked from commit 137b94a4aad2dbae4c26df65dfa0ca3f6a1fd532) --- frappe/public/js/frappe/ui/sidebar/sidebar.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.js b/frappe/public/js/frappe/ui/sidebar/sidebar.js index c904710bf1f0..5c2f1a0f0ba7 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.js @@ -686,6 +686,8 @@ frappe.ui.Sidebar = class Sidebar { let sidebar = this.get_workspace_for_module(module); if (sidebars.includes(this.get_workspace_for_module(module))) { frappe.app.sidebar.setup(sidebar); + } else { + frappe.app.sidebar.setup(module); } } else if (module) { this.show_sidebar_for_module(module); From 62a1faeec5c34a3772cadab3955d7dcb6984fbdc Mon Sep 17 00:00:00 2001 From: MochaMind Date: Sun, 24 May 2026 22:51:07 +0530 Subject: [PATCH 072/261] chore: update POT file (#39472) --- frappe/locale/main.pot | 606 ++++++++++++++++++++--------------------- 1 file changed, 295 insertions(+), 311 deletions(-) diff --git a/frappe/locale/main.pot b/frappe/locale/main.pot index fc9afe134681..3d49447ec30c 100644 --- a/frappe/locale/main.pot +++ b/frappe/locale/main.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: Frappe Framework VERSION\n" "Report-Msgid-Bugs-To: developers@frappe.io\n" -"POT-Creation-Date: 2026-05-10 09:57+0000\n" -"PO-Revision-Date: 2026-05-10 09:57+0000\n" +"POT-Creation-Date: 2026-05-24 10:08+0000\n" +"PO-Revision-Date: 2026-05-24 10:08+0000\n" "Last-Translator: developers@frappe.io\n" "Language-Team: developers@frappe.io\n" "MIME-Version: 1.0\n" @@ -642,7 +642,7 @@ msgstr "" msgid "A field with the name {0} already exists in {1}" msgstr "" -#: frappe/core/doctype/file/file.py:343 +#: frappe/core/doctype/file/file.py:346 msgid "A file with same name {} already exists" msgstr "" @@ -1150,7 +1150,7 @@ msgstr "" msgid "Add Contact" msgstr "" -#: frappe/desk/doctype/event/event.js:38 +#: frappe/desk/doctype/event/event.js:36 msgid "Add Contacts" msgstr "" @@ -1191,7 +1191,7 @@ msgstr "" msgid "Add New Permission Rule" msgstr "" -#: frappe/desk/doctype/event/event.js:35 frappe/desk/doctype/event/event.js:42 +#: frappe/desk/doctype/event/event.js:40 msgid "Add Participants" msgstr "" @@ -1234,7 +1234,7 @@ msgstr "" msgid "Add Tags" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2287 +#: frappe/public/js/frappe/list/list_view.js:2288 msgctxt "Button in list view actions menu" msgid "Add Tags" msgstr "" @@ -1362,7 +1362,7 @@ msgstr "" msgid "Add tab" msgstr "" -#: frappe/public/js/frappe/utils/dashboard_utils.js:262 +#: frappe/public/js/frappe/utils/dashboard_utils.js:275 #: frappe/public/js/frappe/views/reports/query_report.js:253 msgid "Add to Dashboard" msgstr "" @@ -1549,7 +1549,7 @@ msgstr "" msgid "Administrator accessed {0} on {1} via IP Address {2}." msgstr "" -#: frappe/desk/form/document_follow.py:52 +#: frappe/desk/form/document_follow.py:55 msgid "Administrator can't follow" msgstr "" @@ -1713,7 +1713,7 @@ msgstr "" #. Label of the all_day (Check) field in DocType 'Event' #: frappe/desk/doctype/calendar_view/calendar_view.json #: frappe/desk/doctype/event/event.json -#: frappe/public/js/frappe/ui/notifications/notifications.js:444 +#: frappe/public/js/frappe/ui/notifications/notifications.js:446 msgid "All Day" msgstr "" @@ -2136,7 +2136,7 @@ msgstr "" msgid "Already Registered" msgstr "" -#: frappe/public/js/frappe/form/toolbar.js:741 +#: frappe/public/js/frappe/form/toolbar.js:742 msgid "Already amended as {0}" msgstr "" @@ -2191,7 +2191,7 @@ msgstr "" #: frappe/core/doctype/custom_docperm/custom_docperm.json #: frappe/core/doctype/docperm/docperm.json #: frappe/core/doctype/user_document_type/user_document_type.json -#: frappe/public/js/frappe/form/toolbar.js:737 +#: frappe/public/js/frappe/form/toolbar.js:738 msgid "Amend" msgstr "" @@ -2441,7 +2441,7 @@ msgstr "" msgid "Apply" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2272 +#: frappe/public/js/frappe/list/list_view.js:2273 msgctxt "Button in list view actions menu" msgid "Apply Assignment Rule" msgstr "" @@ -2528,7 +2528,7 @@ msgstr "" msgid "Are you sure you want to cancel the invitation?" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2251 +#: frappe/public/js/frappe/list/list_view.js:2252 msgid "Are you sure you want to clear the assignments?" msgstr "" @@ -2658,7 +2658,7 @@ msgstr "" msgid "Assign To" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2233 +#: frappe/public/js/frappe/list/list_view.js:2234 msgctxt "Button in list view actions menu" msgid "Assign To" msgstr "" @@ -2883,7 +2883,7 @@ msgstr "" msgid "Attached To Name" msgstr "" -#: frappe/core/doctype/file/file.py:187 +#: frappe/core/doctype/file/file.py:190 msgid "Attached To Name must be a string or an integer" msgstr "" @@ -2899,7 +2899,7 @@ msgstr "" msgid "Attachment Limit (MB)" msgstr "" -#: frappe/core/doctype/file/file.py:412 +#: frappe/core/doctype/file/file.py:415 #: frappe/public/js/frappe/form/sidebar/attachments.js:36 msgid "Attachment Limit Reached" msgstr "" @@ -3146,7 +3146,7 @@ msgstr "" msgid "Auto Reply Message" msgstr "" -#: frappe/automation/doctype/assignment_rule/assignment_rule.py:177 +#: frappe/automation/doctype/assignment_rule/assignment_rule.py:206 msgid "Auto assignment failed: {0}" msgstr "" @@ -3407,7 +3407,7 @@ msgstr "" #. 'System Health Report' #: frappe/core/workspace/build/build.json #: frappe/desk/doctype/system_health_report/system_health_report.json -#: frappe/public/js/frappe/ui/sidebar/sidebar.js:536 +#: frappe/public/js/frappe/ui/sidebar/sidebar.js:530 msgid "Background Jobs" msgstr "" @@ -3829,11 +3829,11 @@ msgstr "" msgid "Bulk Edit {0}" msgstr "" -#: frappe/desk/reportview.py:691 +#: frappe/desk/reportview.py:698 msgid "Bulk Operation Failed" msgstr "" -#: frappe/desk/reportview.py:695 +#: frappe/desk/reportview.py:702 msgid "Bulk Operation Successful" msgstr "" @@ -4009,10 +4009,6 @@ msgstr "" msgid "Cache Cleared" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:234 -msgid "Calculate" -msgstr "" - #. Option for the 'Select List View' (Select) field in DocType 'Form Tour' #. Option for the 'DocType View' (Select) field in DocType 'Workspace Shortcut' #: frappe/desk/doctype/form_tour/form_tour.json @@ -4090,7 +4086,7 @@ msgstr "" msgid "Can only list down the document types which has been linked to the User document type." msgstr "" -#: frappe/desk/form/document_follow.py:48 +#: frappe/desk/form/document_follow.py:51 msgid "Can't follow since changes are not tracked." msgstr "" @@ -4113,7 +4109,7 @@ msgstr "" msgid "Cancel" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2342 +#: frappe/public/js/frappe/list/list_view.js:2343 msgctxt "Button in list view actions menu" msgid "Cancel" msgstr "" @@ -4139,7 +4135,7 @@ msgstr "" msgid "Cancel Prepared Report" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2347 +#: frappe/public/js/frappe/list/list_view.js:2348 msgctxt "Title of confirmation dialog" msgid "Cancel {0} documents?" msgstr "" @@ -4188,15 +4184,15 @@ msgstr "" msgid "Cannot Remove" msgstr "" -#: frappe/model/base_document.py:1293 +#: frappe/model/base_document.py:1296 msgid "Cannot Update After Submit" msgstr "" -#: frappe/core/doctype/file/file.py:717 +#: frappe/core/doctype/file/file.py:720 msgid "Cannot access file path {0}" msgstr "" -#: frappe/core/doctype/file/file.py:150 +#: frappe/core/doctype/file/file.py:153 msgid "Cannot attach a folder to a document" msgstr "" @@ -4244,7 +4240,7 @@ msgstr "" msgid "Cannot delete Desktop Icon '{0}' as it is restricted" msgstr "" -#: frappe/core/doctype/file/file.py:239 +#: frappe/core/doctype/file/file.py:242 msgid "Cannot delete Home and Attachments folders" msgstr "" @@ -4324,11 +4320,11 @@ msgstr "" msgid "Cannot enable {0} for a non-submittable doctype" msgstr "" -#: frappe/core/doctype/file/file.py:338 +#: frappe/core/doctype/file/file.py:341 msgid "Cannot find file {} on disk" msgstr "" -#: frappe/core/doctype/file/file.py:657 +#: frappe/core/doctype/file/file.py:660 msgid "Cannot get file contents of a Folder" msgstr "" @@ -4356,7 +4352,7 @@ msgstr "" msgid "Cannot move row" msgstr "" -#: frappe/utils/xlsxutils.py:150 +#: frappe/utils/xlsxutils.py:151 msgid "Cannot register an empty XLSX style" msgstr "" @@ -4734,15 +4730,11 @@ msgstr "" msgid "Clear All" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2248 +#: frappe/public/js/frappe/list/list_view.js:2249 msgctxt "Button in list view actions menu" msgid "Clear Assignment" msgstr "" -#: frappe/public/js/frappe/ui/keyboard.js:289 -msgid "Clear Cache and Reload" -msgstr "" - #: frappe/core/doctype/error_log/error_log_list.js:12 msgid "Clear Error Logs" msgstr "" @@ -4768,6 +4760,10 @@ msgstr "" msgid "Clear all filters" msgstr "" +#: frappe/public/js/frappe/ui/keyboard.js:282 +msgid "Clear cache and reload" +msgstr "" + #: frappe/public/js/frappe/views/communication.js:490 msgid "Clear the email message and add the template" msgstr "" @@ -5020,7 +5016,7 @@ msgctxt "Shrink code field." msgid "Collapse" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2275 +#: frappe/public/js/frappe/views/reports/query_report.js:2279 #: frappe/public/js/frappe/views/treeview.js:124 msgid "Collapse All" msgstr "" @@ -5203,11 +5199,11 @@ msgstr "" msgid "Comment Type" msgstr "" -#: frappe/desk/form/utils.py:57 +#: frappe/desk/form/utils.py:58 msgid "Comment can only be edited by the owner" msgstr "" -#: frappe/desk/form/utils.py:73 +#: frappe/desk/form/utils.py:74 msgid "Comment publicity can only be updated by the original author or a System Manager." msgstr "" @@ -5223,7 +5219,7 @@ msgstr "" msgid "Comments and Communications will be associated with this linked document" msgstr "" -#: frappe/templates/includes/comments/comments.py:52 +#: frappe/templates/includes/comments/comments.py:56 msgid "Comments cannot have links or email addresses" msgstr "" @@ -5731,7 +5727,7 @@ msgstr "" msgid "Copied to clipboard." msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2566 +#: frappe/public/js/frappe/list/list_view.js:2567 msgid "Copied {0} {1} to clipboard" msgstr "" @@ -5743,7 +5739,7 @@ msgstr "" msgid "Copy File URL" msgstr "" -#: frappe/public/js/frappe/form/templates/timeline_message_box.html:93 +#: frappe/public/js/frappe/form/templates/timeline_message_box.html:94 msgid "Copy Link" msgstr "" @@ -5757,7 +5753,7 @@ msgstr "" #: frappe/public/js/frappe/form/controls/code.js:32 #: frappe/public/js/frappe/form/toolbar.js:543 -#: frappe/public/js/frappe/list/list_view.js:2450 +#: frappe/public/js/frappe/list/list_view.js:2451 msgid "Copy to Clipboard" msgstr "" @@ -5798,7 +5794,7 @@ msgstr "" msgid "Could not parse field: {0}" msgstr "" -#: frappe/utils/pdf_generator/chrome_pdf_generator.py:168 +#: frappe/utils/pdf_generator/chrome_pdf_generator.py:175 msgid "Could not start Chromium. Check logs for details." msgstr "" @@ -5985,10 +5981,6 @@ msgctxt "Description of a list view shortcut" msgid "Create a new document" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:228 -msgid "Create a new record" -msgstr "" - #: frappe/public/js/frappe/form/controls/link.js:505 #: frappe/public/js/frappe/form/controls/link.js:507 #: frappe/public/js/frappe/form/link_selector.js:146 @@ -6035,11 +6027,11 @@ msgstr "" msgid "Created By" msgstr "" -#: frappe/public/js/frappe/form/sidebar/form_sidebar.js:174 +#: frappe/public/js/frappe/form/sidebar/form_sidebar.js:177 msgid "Created By You" msgstr "" -#: frappe/public/js/frappe/form/sidebar/form_sidebar.js:175 +#: frappe/public/js/frappe/form/sidebar/form_sidebar.js:178 msgid "Created By {0}" msgstr "" @@ -6144,6 +6136,11 @@ msgstr "" msgid "Current" msgstr "" +#. Label of the current_index (Int) field in DocType 'Assignment Rule' +#: frappe/automation/doctype/assignment_rule/assignment_rule.json +msgid "Current Index" +msgstr "" + #. Label of the current_job_id (Link) field in DocType 'RQ Worker' #: frappe/core/doctype/rq_worker/rq_worker.json msgid "Current Job ID" @@ -6395,13 +6392,13 @@ msgstr "" #. Label of a Workspace Sidebar Item #: frappe/printing/page/print/print.js:193 #: frappe/public/js/frappe/form/templates/print_layout.html:39 -#: frappe/public/js/frappe/form/toolbar.js:636 +#: frappe/public/js/frappe/form/toolbar.js:637 #: frappe/public/js/frappe/views/dashboard/dashboard_view.js:198 #: frappe/workspace_sidebar/website.json msgid "Customize" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2009 +#: frappe/public/js/frappe/list/list_view.js:2010 msgctxt "Button in list view menu" msgid "Customize" msgstr "" @@ -6435,7 +6432,7 @@ msgstr "" msgid "Customize Form Field" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2035 +#: frappe/public/js/frappe/list/list_view.js:2036 msgctxt "Customize qucik filters of List View" msgid "Customize Quick Filters" msgstr "" @@ -7115,7 +7112,7 @@ msgstr "" msgid "Delete" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2310 +#: frappe/public/js/frappe/list/list_view.js:2311 msgctxt "Button in list view actions menu" msgid "Delete" msgstr "" @@ -7212,12 +7209,12 @@ msgstr "" msgid "Delete this record to allow sending to this email address" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2315 +#: frappe/public/js/frappe/list/list_view.js:2316 msgctxt "Title of confirmation dialog" msgid "Delete {0} item permanently?" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2321 +#: frappe/public/js/frappe/list/list_view.js:2322 msgctxt "Title of confirmation dialog" msgid "Delete {0} items permanently?" msgstr "" @@ -7254,7 +7251,7 @@ msgstr "" msgid "Deleted Name" msgstr "" -#: frappe/desk/reportview.py:695 +#: frappe/desk/reportview.py:702 msgid "Deleted all documents successfully" msgstr "" @@ -7262,7 +7259,7 @@ msgstr "" msgid "Deleted!" msgstr "" -#: frappe/desk/reportview.py:672 +#: frappe/desk/reportview.py:679 msgid "Deleting {0}" msgstr "" @@ -7732,10 +7729,7 @@ msgid "Display Depends On" msgstr "" #. Label of the depends_on (Code) field in DocType 'DocField' -#. Label of the display_depends_on (Code) field in DocType 'Workspace Sidebar -#. Item' #: frappe/core/doctype/docfield/docfield.json -#: frappe/desk/doctype/workspace_sidebar_item/workspace_sidebar_item.json msgid "Display Depends On (JS)" msgstr "" @@ -8004,7 +7998,7 @@ msgstr "" msgid "Document Follow" msgstr "" -#: frappe/desk/form/document_follow.py:100 +#: frappe/desk/form/document_follow.py:103 msgid "Document Follow Notification" msgstr "" @@ -8242,7 +8236,7 @@ msgstr "" msgid "Document cannot be used as a filter value" msgstr "" -#: frappe/desk/form/document_follow.py:59 +#: frappe/desk/form/document_follow.py:62 msgid "Document follow is not enabled for this user." msgstr "" @@ -8518,7 +8512,7 @@ msgstr "" msgid "Duplicate Filter Name" msgstr "" -#: frappe/model/base_document.py:779 frappe/model/rename_doc.py:111 +#: frappe/model/base_document.py:782 frappe/model/rename_doc.py:111 msgid "Duplicate Name" msgstr "" @@ -8630,7 +8624,7 @@ msgstr "" #: frappe/public/js/frappe/form/templates/address_list.html:13 #: frappe/public/js/frappe/form/templates/contact_list.html:13 #: frappe/public/js/frappe/form/toolbar.js:214 -#: frappe/public/js/frappe/form/toolbar.js:790 +#: frappe/public/js/frappe/form/toolbar.js:791 #: frappe/public/js/frappe/views/reports/query_report.js:915 #: frappe/public/js/frappe/views/reports/query_report.js:1925 #: frappe/public/js/frappe/widgets/base_widget.js:65 @@ -8643,7 +8637,7 @@ msgstr "" msgid "Edit" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2396 +#: frappe/public/js/frappe/list/list_view.js:2397 msgctxt "Button in list view actions menu" msgid "Edit" msgstr "" @@ -8678,11 +8672,11 @@ msgstr "" msgid "Edit Custom HTML" msgstr "" -#: frappe/public/js/frappe/form/toolbar.js:655 +#: frappe/public/js/frappe/form/toolbar.js:656 msgid "Edit DocType" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2028 +#: frappe/public/js/frappe/list/list_view.js:2029 msgctxt "Button in list view menu" msgid "Edit DocType" msgstr "" @@ -9684,7 +9678,7 @@ msgstr "" msgid "Error parsing nested filters: {0}. {1}" msgstr "" -#: frappe/desk/search.py:256 +#: frappe/desk/search.py:262 msgid "Error validating \"Ignore User Permissions\"" msgstr "" @@ -9700,15 +9694,15 @@ msgstr "" msgid "Error {0}: {1}" msgstr "" -#: frappe/model/base_document.py:933 +#: frappe/model/base_document.py:936 msgid "Error: Data missing in table {0}" msgstr "" -#: frappe/model/base_document.py:943 +#: frappe/model/base_document.py:946 msgid "Error: Value missing for {0}: {1}" msgstr "" -#: frappe/model/base_document.py:937 +#: frappe/model/base_document.py:940 msgid "Error: {0} Row #{1}: Value missing for: {2}" msgstr "" @@ -9772,7 +9766,7 @@ msgstr "" msgid "Event Type" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:70 +#: frappe/public/js/frappe/ui/notifications/notifications.js:72 msgid "Events" msgstr "" @@ -9869,7 +9863,7 @@ msgstr "" msgid "Executing..." msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2291 +#: frappe/public/js/frappe/views/reports/query_report.js:2295 msgid "Execution Time: {0} sec" msgstr "" @@ -9895,7 +9889,7 @@ msgctxt "Enlarge code field." msgid "Expand" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2273 +#: frappe/public/js/frappe/views/reports/query_report.js:2277 #: frappe/public/js/frappe/views/treeview.js:134 msgid "Expand All" msgstr "" @@ -9975,7 +9969,7 @@ msgstr "" msgid "Export" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2438 +#: frappe/public/js/frappe/list/list_view.js:2439 msgctxt "Button in list view actions menu" msgid "Export" msgstr "" @@ -10198,7 +10192,7 @@ msgstr "" msgid "Failed to delete communication" msgstr "" -#: frappe/desk/reportview.py:689 +#: frappe/desk/reportview.py:696 msgid "Failed to delete {0} documents: {1}" msgstr "" @@ -10376,7 +10370,7 @@ msgstr "" msgid "Field \"value\" is mandatory. Please specify value to be updated" msgstr "" -#: frappe/desk/search.py:271 +#: frappe/desk/search.py:277 msgid "Field {0} not found in {1}" msgstr "" @@ -10540,7 +10534,7 @@ msgstr "" msgid "Fields Multicheck" msgstr "" -#: frappe/core/doctype/file/file.py:505 +#: frappe/core/doctype/file/file.py:508 msgid "Fields `file_name` or `file_url` must be set for File" msgstr "" @@ -10636,7 +10630,7 @@ msgstr "" msgid "File URL" msgstr "" -#: frappe/core/doctype/file/file.py:123 +#: frappe/core/doctype/file/file.py:126 msgid "File URL is required when copying an existing attachment." msgstr "" @@ -10644,7 +10638,7 @@ msgstr "" msgid "File backup is ready" msgstr "" -#: frappe/core/doctype/file/file.py:720 +#: frappe/core/doctype/file/file.py:723 msgid "File name cannot have {0}" msgstr "" @@ -10652,7 +10646,7 @@ msgstr "" msgid "File not attached" msgstr "" -#: frappe/core/doctype/file/file.py:830 frappe/public/js/frappe/request.js:198 +#: frappe/core/doctype/file/file.py:833 frappe/public/js/frappe/request.js:198 #: frappe/utils/file_manager.py:221 msgid "File size exceeded the maximum allowed size of {0} MB" msgstr "" @@ -10661,7 +10655,7 @@ msgstr "" msgid "File too big" msgstr "" -#: frappe/core/doctype/file/file.py:464 +#: frappe/core/doctype/file/file.py:467 msgid "File type of {0} is not allowed" msgstr "" @@ -10669,7 +10663,7 @@ msgstr "" msgid "File upload failed." msgstr "" -#: frappe/core/doctype/file/file.py:451 frappe/core/doctype/file/file.py:522 +#: frappe/core/doctype/file/file.py:454 frappe/core/doctype/file/file.py:525 msgid "File {0} does not exist" msgstr "" @@ -10828,8 +10822,8 @@ msgstr "" msgid "Find '{0}' in ..." msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:381 -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:383 +#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:360 +#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:362 #: frappe/public/js/frappe/ui/toolbar/search_utils.js:152 #: frappe/public/js/frappe/ui/toolbar/search_utils.js:155 msgid "Find {0} in {1}" @@ -10947,7 +10941,7 @@ msgstr "" msgid "Folder name should not include '/' (slash)" msgstr "" -#: frappe/core/doctype/file/file.py:568 +#: frappe/core/doctype/file/file.py:571 msgid "Folder {0} is not empty" msgstr "" @@ -10957,7 +10951,7 @@ msgid "Folio" msgstr "" #: frappe/public/js/frappe/form/templates/form_sidebar.html:151 -#: frappe/public/js/frappe/form/toolbar.js:950 +#: frappe/public/js/frappe/form/toolbar.js:951 msgid "Follow" msgstr "" @@ -10969,7 +10963,7 @@ msgstr "" msgid "Following Report Filters have missing values:" msgstr "" -#: frappe/desk/form/document_follow.py:66 +#: frappe/desk/form/document_follow.py:69 msgid "Following document {0}" msgstr "" @@ -11157,7 +11151,7 @@ msgid "" "For ranges, use 5:10 (for values between 5 & 10)." msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2288 +#: frappe/public/js/frappe/views/reports/query_report.js:2292 msgid "For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10)." msgstr "" @@ -11585,7 +11579,7 @@ msgstr "" msgid "Generate New Report" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:449 +#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:428 msgid "Generate Random Password" msgstr "" @@ -11595,7 +11589,7 @@ msgstr "" msgid "Generate Separate Documents For Each Assignee" msgstr "" -#: frappe/public/js/frappe/ui/sidebar/sidebar.js:531 +#: frappe/public/js/frappe/ui/sidebar/sidebar.js:525 #: frappe/public/js/frappe/utils/utils.js:2075 msgid "Generate Tracking URL" msgstr "" @@ -11703,7 +11697,7 @@ msgstr "" msgid "Global Search Settings" msgstr "" -#: frappe/public/js/frappe/ui/keyboard.js:122 +#: frappe/public/js/frappe/ui/keyboard.js:133 msgid "Global Shortcuts" msgstr "" @@ -11712,7 +11706,7 @@ msgstr "" msgid "Global Unsubscribe" msgstr "" -#: frappe/public/js/frappe/form/toolbar.js:885 +#: frappe/public/js/frappe/form/toolbar.js:886 msgid "Go" msgstr "" @@ -11980,7 +11974,7 @@ msgstr "" msgid "Grid Page Length" msgstr "" -#: frappe/public/js/frappe/ui/keyboard.js:127 +#: frappe/public/js/frappe/ui/keyboard.js:138 msgid "Grid Shortcuts" msgstr "" @@ -12631,13 +12625,13 @@ msgstr "" #: frappe/public/js/frappe/list/list_settings.js:340 #: frappe/public/js/frappe/list/list_view.js:427 #: frappe/public/js/frappe/list/list_view.js:491 -#: frappe/public/js/frappe/list/list_view.js:2488 +#: frappe/public/js/frappe/list/list_view.js:2489 #: frappe/public/js/frappe/model/meta.js:208 #: frappe/public/js/frappe/model/model.js:122 msgid "ID" msgstr "" -#: frappe/desk/reportview.py:563 +#: frappe/desk/reportview.py:570 #: frappe/public/js/frappe/views/reports/report_view.js:1079 msgctxt "Label of name column in report" msgid "ID" @@ -13628,7 +13622,7 @@ msgstr "" msgid "Insufficient Permission for {0}" msgstr "" -#: frappe/desk/reportview.py:363 +#: frappe/desk/reportview.py:370 msgid "Insufficient Permissions for deleting Report" msgstr "" @@ -13807,7 +13801,7 @@ msgstr "" msgid "Invalid Fieldname" msgstr "" -#: frappe/core/doctype/file/file.py:295 +#: frappe/core/doctype/file/file.py:298 msgid "Invalid File URL" msgstr "" @@ -13902,7 +13896,7 @@ msgstr "" msgid "Invalid Transition" msgstr "" -#: frappe/core/doctype/file/file.py:306 +#: frappe/core/doctype/file/file.py:309 #: frappe/public/js/frappe/file_uploader/FileUploader.vue:551 #: frappe/public/js/frappe/widgets/widget_dialog.js:602 #: frappe/utils/csvutils.py:226 frappe/utils/csvutils.py:247 @@ -13941,6 +13935,10 @@ msgstr "" msgid "Invalid argument type: {0}. Only strings, numbers, dicts, and None are allowed." msgstr "" +#: frappe/utils/response.py:289 +msgid "Invalid backup path" +msgstr "" + #: frappe/database/query.py:854 msgid "Invalid characters in fieldname: {0}. Only letters, numbers, and underscores are allowed." msgstr "" @@ -13965,11 +13963,11 @@ msgstr "" msgid "Invalid expression in Workflow Update Value: {0}" msgstr "" -#: frappe/public/js/frappe/utils/dashboard_utils.js:229 +#: frappe/public/js/frappe/utils/dashboard_utils.js:223 msgid "Invalid expression set in filter {0}" msgstr "" -#: frappe/public/js/frappe/utils/dashboard_utils.js:219 +#: frappe/public/js/frappe/utils/dashboard_utils.js:234 msgid "Invalid expression set in filter {0} ({1})" msgstr "" @@ -14545,12 +14543,12 @@ msgstr "" msgid "Job stopped successfully" msgstr "" -#: frappe/desk/doctype/event/event.js:55 +#: frappe/desk/doctype/event/event.js:53 msgid "Join video conference with {0}" msgstr "" #: frappe/public/js/frappe/form/toolbar.js:421 -#: frappe/public/js/frappe/form/toolbar.js:875 +#: frappe/public/js/frappe/form/toolbar.js:876 msgid "Jump to field" msgstr "" @@ -14630,7 +14628,7 @@ msgstr "" #. Label of a standard help item #. Type: Action -#: frappe/hooks.py frappe/public/js/frappe/ui/keyboard.js:130 +#: frappe/hooks.py frappe/public/js/frappe/ui/keyboard.js:141 msgid "Keyboard Shortcuts" msgstr "" @@ -15583,7 +15581,7 @@ msgstr "" msgid "List Settings" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2126 +#: frappe/public/js/frappe/list/list_view.js:2127 msgctxt "Button in list view menu" msgid "List Settings" msgstr "" @@ -15597,10 +15595,6 @@ msgstr "" msgid "List View Settings" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:229 -msgid "List a document type" -msgstr "" - #. Description of the 'Breadcrumbs' (Code) field in DocType 'Web Form' #. Description of the 'Breadcrumbs' (Code) field in DocType 'Web Page' #: frappe/website/doctype/web_form/web_form.json @@ -15937,7 +15931,7 @@ msgstr "" msgid "Looks like you haven’t added any third party apps." msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:356 +#: frappe/public/js/frappe/ui/notifications/notifications.js:358 msgid "Looks like you haven’t received any notifications." msgstr "" @@ -16039,7 +16033,7 @@ msgstr "" msgid "Manage 3rd party apps" msgstr "" -#: frappe/public/js/billing.bundle.js:77 +#: frappe/public/js/billing.bundle.js:78 msgid "Manage Billing" msgstr "" @@ -16071,7 +16065,7 @@ msgstr "" msgid "Mandatory Depends On (JS)" msgstr "" -#: frappe/website/doctype/web_form/web_form.py:552 +#: frappe/website/doctype/web_form/web_form.py:549 msgid "Mandatory Information missing:" msgstr "" @@ -16159,7 +16153,7 @@ msgstr "" #: frappe/core/doctype/communication/communication.js:78 #: frappe/core/doctype/communication/communication_list.js:19 -#: frappe/public/js/frappe/ui/notifications/notifications.js:311 +#: frappe/public/js/frappe/ui/notifications/notifications.js:313 msgid "Mark as Read" msgstr "" @@ -16282,7 +16276,7 @@ msgstr "" msgid "Maximum" msgstr "" -#: frappe/core/doctype/file/file.py:406 +#: frappe/core/doctype/file/file.py:409 msgid "Maximum Attachment Limit of {0} has been reached for {1} {2}." msgstr "" @@ -16990,11 +16984,11 @@ msgstr "" msgid "Must be of type \"Attach Image\"" msgstr "" -#: frappe/desk/query_report.py:218 +#: frappe/desk/query_report.py:223 msgid "Must have report permission to access this report." msgstr "" -#: frappe/core/doctype/report/report.py:173 +#: frappe/core/doctype/report/report.py:172 msgid "Must specify a Query to run" msgstr "" @@ -17008,7 +17002,7 @@ msgid "Mx" msgstr "" #: frappe/templates/includes/web_sidebar.html:41 -#: frappe/website/doctype/web_form/web_form.py:541 +#: frappe/website/doctype/web_form/web_form.py:538 #: frappe/website/doctype/website_settings/website_settings.py:181 #: frappe/www/me.html:8 frappe/www/update_password.py:10 msgid "My Account" @@ -17694,7 +17688,7 @@ msgstr "" msgid "No Name Specified for {0}" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:354 +#: frappe/public/js/frappe/ui/notifications/notifications.js:356 msgid "No New notifications" msgstr "" @@ -17750,11 +17744,11 @@ msgstr "" msgid "No Suggestions" msgstr "" -#: frappe/desk/reportview.py:760 frappe/public/js/frappe/list/base_list.js:1051 +#: frappe/desk/reportview.py:767 frappe/public/js/frappe/list/base_list.js:1051 msgid "No Tags" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:482 +#: frappe/public/js/frappe/ui/notifications/notifications.js:484 msgid "No Upcoming Events" msgstr "" @@ -17810,7 +17804,7 @@ msgstr "" msgid "No currency fields in {0}" msgstr "" -#: frappe/desk/query_report.py:400 +#: frappe/desk/query_report.py:405 msgid "No data to export" msgstr "" @@ -17855,7 +17849,7 @@ msgstr "" msgid "No filters selected" msgstr "" -#: frappe/desk/form/utils.py:122 +#: frappe/desk/form/utils.py:129 msgid "No further records" msgstr "" @@ -17911,7 +17905,7 @@ msgstr "" msgid "No permission to read {0}" msgstr "" -#: frappe/share.py:221 +#: frappe/share.py:223 msgid "No permission to {0} {1} {2}" msgstr "" @@ -17935,7 +17929,7 @@ msgstr "" msgid "No rows" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2455 +#: frappe/public/js/frappe/list/list_view.js:2456 msgid "No rows selected" msgstr "" @@ -18060,14 +18054,14 @@ msgstr "" #: frappe/__init__.py:552 frappe/app.py:389 frappe/desk/calendar.py:28 #: frappe/public/js/frappe/web_form/webform_script.js:15 -#: frappe/website/doctype/web_form/web_form.py:794 +#: frappe/website/doctype/web_form/web_form.py:799 #: frappe/website/page_renderers/not_permitted_page.py:22 #: frappe/www/login.py:202 frappe/www/qrcode.py:22 frappe/www/qrcode.py:25 #: frappe/www/qrcode.py:37 msgid "Not Permitted" msgstr "" -#: frappe/desk/query_report.py:765 +#: frappe/desk/query_report.py:770 msgid "Not Permitted to read {0}" msgstr "" @@ -18077,7 +18071,7 @@ msgid "Not Published" msgstr "" #: frappe/public/js/frappe/form/toolbar.js:316 -#: frappe/public/js/frappe/form/toolbar.js:858 +#: frappe/public/js/frappe/form/toolbar.js:859 #: frappe/public/js/frappe/model/indicator.js:28 #: frappe/public/js/frappe/views/kanban/kanban_view.js:184 #: frappe/public/js/frappe/views/reports/report_view.js:215 @@ -18164,7 +18158,7 @@ msgstr "" #: frappe/public/js/frappe/request.js:168 #: frappe/public/js/frappe/request.js:173 #: frappe/public/js/frappe/views/kanban/kanban_board.bundle.js:67 -#: frappe/utils/messages.py:173 frappe/website/doctype/web_form/web_form.py:807 +#: frappe/utils/messages.py:173 frappe/website/doctype/web_form/web_form.py:812 #: frappe/website/js/website.js:97 msgid "Not permitted" msgstr "" @@ -18224,7 +18218,7 @@ msgstr "" msgid "Notes:" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:531 +#: frappe/public/js/frappe/ui/notifications/notifications.js:533 msgid "Nothing New" msgstr "" @@ -18255,7 +18249,7 @@ msgstr "" #: frappe/core/doctype/communication/mixins.py:158 #: frappe/desk/doctype/event_notifications/event_notifications.json #: frappe/email/doctype/notification/notification.json -#: frappe/public/js/frappe/ui/sidebar/sidebar.js:497 +#: frappe/public/js/frappe/ui/sidebar/sidebar.js:491 #: frappe/workspace_sidebar/system.json msgid "Notification" msgstr "" @@ -18283,7 +18277,7 @@ msgstr "" msgid "Notification Subscribed Document" msgstr "" -#: frappe/public/js/frappe/form/templates/timeline_message_box.html:8 +#: frappe/public/js/frappe/form/templates/timeline_message_box.html:9 msgid "Notification sent to" msgstr "" @@ -18304,13 +18298,13 @@ msgstr "" #. Label of the notifications (Table) field in DocType 'Event' #. Label of a Workspace Sidebar Item #: frappe/core/doctype/user/user.json frappe/desk/doctype/event/event.json -#: frappe/public/js/frappe/ui/notifications/notifications.js:64 -#: frappe/public/js/frappe/ui/notifications/notifications.js:223 +#: frappe/public/js/frappe/ui/notifications/notifications.js:66 +#: frappe/public/js/frappe/ui/notifications/notifications.js:225 #: frappe/workspace_sidebar/system.json msgid "Notifications" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:337 +#: frappe/public/js/frappe/ui/notifications/notifications.js:339 msgid "Notifications Disabled" msgstr "" @@ -18740,7 +18734,7 @@ msgstr "" msgid "Only Administrator can edit" msgstr "" -#: frappe/core/doctype/report/report.py:77 +#: frappe/core/doctype/report/report.py:423 msgid "Only Administrator can save a standard report. Please rename and save." msgstr "" @@ -18753,7 +18747,7 @@ msgstr "" msgid "Only Allow Edit For" msgstr "" -#: frappe/desk/query_report.py:448 +#: frappe/desk/query_report.py:453 msgid "Only CSV and Excel formats are supported for export" msgstr "" @@ -18770,7 +18764,7 @@ msgstr "" msgid "Only Send Records Updated in Last X Hours" msgstr "" -#: frappe/core/doctype/file/file.py:201 +#: frappe/core/doctype/file/file.py:204 msgid "Only System Managers can make this file public." msgstr "" @@ -18807,7 +18801,7 @@ msgstr "" msgid "Only one {0} can be set as primary." msgstr "" -#: frappe/desk/reportview.py:360 +#: frappe/desk/reportview.py:367 msgid "Only reports of type Report Builder can be deleted" msgstr "" @@ -18855,14 +18849,14 @@ msgid "Open" msgstr "" #: frappe/desk/page/desktop/desktop.js:521 -#: frappe/public/js/frappe/ui/keyboard.js:207 #: frappe/public/js/frappe/ui/keyboard.js:218 +#: frappe/public/js/frappe/ui/keyboard.js:229 msgid "Open Awesomebar" msgstr "" -#: frappe/public/js/frappe/form/templates/timeline_message_box.html:75 -#: frappe/public/js/frappe/form/templates/timeline_message_box.html:96 +#: frappe/public/js/frappe/form/templates/timeline_message_box.html:76 #: frappe/public/js/frappe/form/templates/timeline_message_box.html:97 +#: frappe/public/js/frappe/form/templates/timeline_message_box.html:98 msgid "Open Communication" msgstr "" @@ -18876,10 +18870,6 @@ msgstr "" msgid "Open Documents" msgstr "" -#: frappe/public/js/frappe/ui/keyboard.js:245 -msgid "Open Help" -msgstr "" - #: frappe/public/js/frappe/form/controls/data.js:84 #: frappe/public/js/frappe/form/controls/link.js:20 msgid "Open Link" @@ -18891,10 +18881,6 @@ msgstr "" msgid "Open Reference Document" msgstr "" -#: frappe/public/js/frappe/ui/keyboard.js:228 -msgid "Open Settings" -msgstr "" - #: frappe/public/js/frappe/ui/toolbar/about.js:12 msgid "Open Source Applications for the Web" msgstr "" @@ -18913,11 +18899,7 @@ msgstr "" msgid "Open a dialog with mandatory fields to create a new record quickly. There must be at least one mandatory field to show in dialog." msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:232 -msgid "Open a module or tool" -msgstr "" - -#: frappe/public/js/frappe/ui/keyboard.js:369 +#: frappe/public/js/frappe/ui/keyboard.js:362 msgid "Open console" msgstr "" @@ -18925,10 +18907,6 @@ msgstr "" msgid "Open in a new tab" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:233 -msgid "Open in new tab" -msgstr "" - #: frappe/public/js/frappe/list/list_view.js:1500 msgctxt "Description of a list view shortcut" msgid "Open list item" @@ -19070,7 +19048,7 @@ msgstr "" msgid "Options is required for field {0} of type {1}" msgstr "" -#: frappe/model/base_document.py:999 +#: frappe/model/base_document.py:1002 msgid "Options not set for link field {0}" msgstr "" @@ -19223,7 +19201,7 @@ msgstr "" msgid "PDF Settings" msgstr "" -#: frappe/utils/print_format.py:343 +#: frappe/utils/print_format.py:347 msgid "PDF generation failed" msgstr "" @@ -19379,7 +19357,7 @@ msgstr "" msgid "Page Settings" msgstr "" -#: frappe/public/js/frappe/ui/keyboard.js:125 +#: frappe/public/js/frappe/ui/keyboard.js:136 msgid "Page Shortcuts" msgstr "" @@ -20059,7 +20037,7 @@ msgstr "" msgid "Please check the filter values set for Dashboard Chart: {}" msgstr "" -#: frappe/model/base_document.py:1079 +#: frappe/model/base_document.py:1082 msgid "Please check the value of \"Fetch From\" set for field {0}" msgstr "" @@ -20206,8 +20184,8 @@ msgstr "" msgid "Please find attached {0}: {1}" msgstr "" -#: frappe/templates/includes/comments/comments.py:42 -#: frappe/templates/includes/comments/comments.py:45 +#: frappe/templates/includes/comments/comments.py:46 +#: frappe/templates/includes/comments/comments.py:49 msgid "Please login to post a comment." msgstr "" @@ -20579,7 +20557,7 @@ msgstr "" msgid "Prepared Report User" msgstr "" -#: frappe/desk/query_report.py:318 +#: frappe/desk/query_report.py:323 msgid "Prepared report render failed" msgstr "" @@ -20591,7 +20569,7 @@ msgstr "" msgid "Prepend the template to the email message" msgstr "" -#: frappe/public/js/frappe/ui/keyboard.js:141 +#: frappe/public/js/frappe/ui/keyboard.js:152 msgid "Press Alt Key to trigger additional shortcuts in Menu and Sidebar" msgstr "" @@ -20733,7 +20711,7 @@ msgstr "" msgid "Print" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2302 +#: frappe/public/js/frappe/list/list_view.js:2303 msgctxt "Button in list view actions menu" msgid "Print" msgstr "" @@ -20932,7 +20910,7 @@ msgstr "" msgid "Printing" msgstr "" -#: frappe/utils/print_format.py:345 +#: frappe/utils/print_format.py:349 msgid "Printing failed" msgstr "" @@ -21062,7 +21040,7 @@ msgstr "" msgid "Protect Attached Files" msgstr "" -#: frappe/core/doctype/file/file.py:597 +#: frappe/core/doctype/file/file.py:600 msgid "Protected File" msgstr "" @@ -21126,7 +21104,7 @@ msgstr "" #. Label of the published (Check) field in DocType 'Web Form' #. Label of the published (Check) field in DocType 'Web Page' #: frappe/core/doctype/comment/comment.json -#: frappe/public/js/frappe/form/templates/timeline_message_box.html:42 +#: frappe/public/js/frappe/form/templates/timeline_message_box.html:43 #: frappe/website/doctype/help_article/help_article.json #: frappe/website/doctype/help_category/help_category.json #: frappe/website/doctype/web_form/web_form.json @@ -21305,7 +21283,7 @@ msgstr "" msgid "Query analysis complete. Check suggested indexes." msgstr "" -#: frappe/utils/safe_exec.py:497 +#: frappe/utils/safe_exec.py:508 msgid "Query must be of SELECT or read-only WITH type." msgstr "" @@ -21585,6 +21563,10 @@ msgstr "" msgid "Read the documentation to know more" msgstr "" +#: frappe/utils/safe_exec.py:495 +msgid "Read-Only queries are allowed" +msgstr "" + #. Label of the readme (Markdown Editor) field in DocType 'Package' #: frappe/core/doctype/package/package.json msgid "Readme" @@ -22281,7 +22263,7 @@ msgstr "" msgid "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"" msgstr "" -#: frappe/public/js/frappe/form/sidebar/form_sidebar.js:196 +#: frappe/public/js/frappe/form/sidebar/form_sidebar.js:202 msgid "Repeats {0}" msgstr "" @@ -22399,7 +22381,7 @@ msgstr "" msgid "Report Description" msgstr "" -#: frappe/core/doctype/report/report.py:173 +#: frappe/core/doctype/report/report.py:172 msgid "Report Document Error" msgstr "" @@ -22510,7 +22492,7 @@ msgstr "" msgid "Report timed out." msgstr "" -#: frappe/desk/query_report.py:823 +#: frappe/desk/query_report.py:828 msgid "Report updated successfully" msgstr "" @@ -22527,7 +22509,7 @@ msgstr "" msgid "Report {0}" msgstr "" -#: frappe/desk/reportview.py:367 +#: frappe/desk/reportview.py:374 msgid "Report {0} deleted" msgstr "" @@ -22535,7 +22517,7 @@ msgstr "" msgid "Report {0} is disabled" msgstr "" -#: frappe/desk/reportview.py:344 +#: frappe/desk/reportview.py:351 msgid "Report {0} saved" msgstr "" @@ -22798,7 +22780,7 @@ msgstr "" msgid "Response Type" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:450 +#: frappe/public/js/frappe/ui/notifications/notifications.js:452 msgid "Rest of the day" msgstr "" @@ -22861,8 +22843,8 @@ msgctxt "Title of message showing restrictions in list view" msgid "Restrictions" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:437 -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:452 +#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:416 +#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:431 msgid "Result" msgstr "" @@ -23165,7 +23147,7 @@ msgstr "" msgid "Route: Example \"/desk\"" msgstr "" -#: frappe/model/base_document.py:982 frappe/model/document.py:824 +#: frappe/model/base_document.py:985 frappe/model/document.py:824 msgid "Row" msgstr "" @@ -23178,7 +23160,7 @@ msgstr "" msgid "Row # {0}: Non administrator user can not set the role {1} to the custom doctype" msgstr "" -#: frappe/model/base_document.py:1110 +#: frappe/model/base_document.py:1113 msgid "Row #{0}:" msgstr "" @@ -23470,7 +23452,7 @@ msgstr "" #: frappe/public/js/frappe/form/quick_entry.js:186 #: frappe/public/js/frappe/list/list_settings.js:37 #: frappe/public/js/frappe/list/list_settings.js:250 -#: frappe/public/js/frappe/list/list_view.js:2057 +#: frappe/public/js/frappe/list/list_view.js:2058 #: frappe/public/js/frappe/ui/toolbar/toolbar.js:335 #: frappe/public/js/frappe/utils/common.js:452 #: frappe/public/js/frappe/views/kanban/kanban_settings.js:45 @@ -23539,7 +23521,7 @@ msgctxt "Freeze message while saving a document" msgid "Saving" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2068 +#: frappe/public/js/frappe/list/list_view.js:2069 msgid "Saving Changes..." msgstr "" @@ -23759,9 +23741,9 @@ msgstr "" #: frappe/public/js/frappe/list/base_list.js:921 #: frappe/public/js/frappe/list/list_sidebar_group_by.js:141 #: frappe/public/js/frappe/list/list_sidebar_stat.html:4 -#: frappe/public/js/frappe/list/list_view.js:2077 +#: frappe/public/js/frappe/list/list_view.js:2078 #: frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js:20 -#: frappe/public/js/frappe/ui/sidebar/sidebar.js:485 +#: frappe/public/js/frappe/ui/sidebar/sidebar.js:479 #: frappe/public/js/frappe/ui/toolbar/search.js:49 #: frappe/public/js/frappe/ui/toolbar/search.js:68 #: frappe/public/js/frappe/views/reports/report_view.js:1717 @@ -23782,10 +23764,6 @@ msgstr "" msgid "Search Fields" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:238 -msgid "Search Help" -msgstr "" - #. Label of the allowed_in_global_search (Table) field in DocType 'Global #. Search Settings' #: frappe/desk/doctype/global_search_settings/global_search_settings.json @@ -23825,15 +23803,11 @@ msgstr "" msgid "Search for icons..." msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:354 -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:358 +#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:333 +#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:337 msgid "Search for {0}" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:230 -msgid "Search in a document type" -msgstr "" - #: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:35 msgid "Search or type a command" msgstr "" @@ -23948,7 +23922,7 @@ msgstr "" msgid "Security.txt will expire soon!" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:347 +#: frappe/public/js/frappe/ui/notifications/notifications.js:349 msgid "See all Activity" msgstr "" @@ -24053,7 +24027,7 @@ msgstr "" #. Label of the dashboard_name (Link) field in DocType 'Form Tour' #: frappe/desk/doctype/form_tour/form_tour.json -#: frappe/public/js/frappe/utils/dashboard_utils.js:239 +#: frappe/public/js/frappe/utils/dashboard_utils.js:252 msgid "Select Dashboard" msgstr "" @@ -24089,7 +24063,7 @@ msgstr "" #: frappe/public/js/form_builder/components/controls/FetchFromControl.vue:33 #: frappe/public/js/frappe/doctype/index.js:207 -#: frappe/public/js/frappe/form/toolbar.js:880 +#: frappe/public/js/frappe/form/toolbar.js:881 msgid "Select Field" msgstr "" @@ -24115,7 +24089,7 @@ msgstr "" msgid "Select Fields To Update" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2053 +#: frappe/public/js/frappe/list/list_view.js:2054 msgid "Select Filters" msgstr "" @@ -24726,7 +24700,7 @@ msgstr "" msgid "Set Filters for {0}" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2271 +#: frappe/public/js/frappe/views/reports/query_report.js:2275 msgid "Set Level" msgstr "" @@ -25134,10 +25108,6 @@ msgstr "" msgid "Show Full Number" msgstr "" -#: frappe/public/js/frappe/ui/keyboard.js:236 -msgid "Show Keyboard Shortcuts" -msgstr "" - #. Label of the show_labels (Check) field in DocType 'Kanban Board' #: frappe/desk/doctype/kanban_board/kanban_board.json #: frappe/public/js/frappe/views/kanban/kanban_settings.js:30 @@ -25321,6 +25291,10 @@ msgstr "" msgid "Show in filter" msgstr "" +#: frappe/public/js/frappe/ui/keyboard.js:238 +msgid "Show keyboard shortcuts" +msgstr "" + #. Label of the show_document_link (Check) field in DocType 'Slack Webhook URL' #: frappe/integrations/doctype/slack_webhook_url/slack_webhook_url.json msgid "Show link to document" @@ -25840,7 +25814,7 @@ msgstr "" msgid "Splash Image" msgstr "" -#: frappe/desk/reportview.py:459 +#: frappe/desk/reportview.py:466 #: frappe/email/doctype/auto_email_report/auto_email_report.py:162 #: frappe/public/js/frappe/web_form/web_form_list.js:182 #: frappe/templates/print_formats/standard_macros.html:44 @@ -25900,7 +25874,7 @@ msgstr "" msgid "Standard Print Style cannot be changed. Please duplicate to edit." msgstr "" -#: frappe/desk/reportview.py:357 +#: frappe/desk/reportview.py:364 msgid "Standard Reports cannot be deleted" msgstr "" @@ -25918,6 +25892,10 @@ msgstr "" msgid "Standard Web Forms can not be modified, duplicate the Web Form instead." msgstr "" +#: frappe/core/doctype/report/report.py:431 +msgid "Standard reports can only be created in developer mode." +msgstr "" + #: frappe/website/doctype/web_page/web_page.js:94 msgid "Standard rich text editor with controls" msgstr "" @@ -26109,7 +26087,7 @@ msgstr "" #: frappe/integrations/doctype/integration_request/integration_request.json #: frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.json #: frappe/public/js/frappe/list/list_settings.js:362 -#: frappe/public/js/frappe/list/list_view.js:2494 +#: frappe/public/js/frappe/list/list_view.js:2495 #: frappe/public/js/frappe/views/reports/report_view.js:1070 #: frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.json #: frappe/website/doctype/personal_data_deletion_step/personal_data_deletion_step.json @@ -26304,7 +26282,7 @@ msgstr "" msgid "Submit" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2369 +#: frappe/public/js/frappe/list/list_view.js:2370 msgctxt "Button in list view actions menu" msgid "Submit" msgstr "" @@ -26362,7 +26340,7 @@ msgstr "" msgid "Submit this document to confirm" msgstr "" -#: frappe/public/js/frappe/list/list_view.js:2374 +#: frappe/public/js/frappe/list/list_view.js:2375 msgctxt "Title of confirmation dialog" msgid "Submit {0} documents?" msgstr "" @@ -26559,7 +26537,6 @@ msgstr "" msgid "Switch Camera" msgstr "" -#: frappe/public/js/frappe/desk.js:96 #: frappe/public/js/frappe/ui/theme_switcher.js:11 msgid "Switch Theme" msgstr "" @@ -26568,6 +26545,10 @@ msgstr "" msgid "Switch To Desk" msgstr "" +#: frappe/public/js/frappe/desk.js:96 +msgid "Switch theme" +msgstr "" + #: frappe/public/js/frappe/ui/sidebar/sidebar.js:121 msgid "Switch to Frappe CRM" msgstr "" @@ -26967,7 +26948,7 @@ msgstr "" msgid "Table MultiSelect" msgstr "" -#: frappe/desk/search.py:284 +#: frappe/desk/search.py:290 msgid "Table MultiSelect requires a table with at least one Link field, but none was found in {0}" msgstr "" @@ -27005,7 +26986,6 @@ msgstr "" #: frappe/public/js/frappe/list/bulk_operations.js:446 #: frappe/public/js/frappe/model/meta.js:215 #: frappe/public/js/frappe/model/model.js:133 -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:231 msgid "Tags" msgstr "" @@ -27213,7 +27193,7 @@ msgstr "" msgid "The Condition '{0}' is invalid" msgstr "" -#: frappe/core/doctype/file/file.py:294 +#: frappe/core/doctype/file/file.py:297 msgid "The File URL you've entered is incorrect" msgstr "" @@ -27259,7 +27239,7 @@ msgstr "" msgid "The column {0} has {1} different date formats. Automatically setting {2} as the default format as it is the most common. Please change other values in this column to this format." msgstr "" -#: frappe/templates/includes/comments/comments.py:48 +#: frappe/templates/includes/comments/comments.py:52 msgid "The comment cannot be empty" msgstr "" @@ -27301,11 +27281,11 @@ msgstr "" msgid "The email button is enabled for the user in the document." msgstr "" -#: frappe/desk/search.py:297 +#: frappe/desk/search.py:303 msgid "The field {0} in {1} does not allow ignoring user permissions" msgstr "" -#: frappe/desk/search.py:312 +#: frappe/desk/search.py:318 msgid "The field {0} in {1} links to {2} and not {3}" msgstr "" @@ -27313,7 +27293,7 @@ msgstr "" msgid "The field {0} is mandatory" msgstr "" -#: frappe/core/doctype/file/file.py:192 +#: frappe/core/doctype/file/file.py:195 msgid "The fieldname you've specified in Attached To Field is invalid" msgstr "" @@ -27412,7 +27392,7 @@ msgstr "" msgid "The selected document {0} is not a {1}." msgstr "" -#: frappe/utils/response.py:343 +#: frappe/utils/response.py:351 msgid "The system is being updated. Please refresh again after a few moments." msgstr "" @@ -27452,7 +27432,7 @@ msgstr "" msgid "The user can view Sales Invoices but cannot modify any field values in them." msgstr "" -#: frappe/model/base_document.py:827 +#: frappe/model/base_document.py:830 msgid "The value of the field {0} is too long in the {1} document. To resolve this issue, please reduce the value length or change the {0} field Type to Long Text using customize form, and then try again." msgstr "" @@ -27497,7 +27477,7 @@ msgstr "" msgid "There are documents which have workflow states that do not exist in this Workflow. It is recommended that you add these states to the Workflow and change their states before removing these states." msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:484 +#: frappe/public/js/frappe/ui/notifications/notifications.js:486 msgid "There are no upcoming events for you." msgstr "" @@ -27530,11 +27510,11 @@ msgstr "" msgid "There is no task called \"{}\"" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:533 +#: frappe/public/js/frappe/ui/notifications/notifications.js:535 msgid "There is nothing new to show you right now." msgstr "" -#: frappe/core/doctype/file/file.py:714 frappe/utils/file_manager.py:372 +#: frappe/core/doctype/file/file.py:717 frappe/utils/file_manager.py:372 msgid "There is some problem with the file url: {0}" msgstr "" @@ -27615,7 +27595,7 @@ msgstr "" msgid "This Month" msgstr "" -#: frappe/core/doctype/file/file.py:470 +#: frappe/core/doctype/file/file.py:473 msgid "This PDF cannot be uploaded as it contains unsafe content." msgstr "" @@ -27715,7 +27695,7 @@ msgid "" "eval:doc.age>18" msgstr "" -#: frappe/core/doctype/file/file.py:596 +#: frappe/core/doctype/file/file.py:599 msgid "This file is attached to a protected document and cannot be deleted." msgstr "" @@ -27750,7 +27730,7 @@ msgstr "" msgid "This goes above the slideshow." msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2348 +#: frappe/public/js/frappe/views/reports/query_report.js:2352 msgid "This is a background report. Please set the appropriate filters and then generate a new one." msgstr "" @@ -28274,15 +28254,24 @@ msgstr "" msgid "Toggle Chart" msgstr "" +#: frappe/public/js/frappe/ui/sidebar/sidebar_header.js:243 +msgid "Toggle Full Width" +msgstr "" + #: frappe/public/js/frappe/views/file/file_view.js:33 msgid "Toggle Grid View" msgstr "" #: frappe/public/js/frappe/form/toolbar.js:472 +#: frappe/public/js/frappe/ui/sidebar/sidebar_header.js:251 msgid "Toggle Sidebar" msgstr "" -#: frappe/public/js/frappe/ui/sidebar/sidebar.js:319 +#: frappe/public/js/frappe/ui/sidebar/sidebar_header.js:235 +msgid "Toggle Theme" +msgstr "" + +#: frappe/public/js/frappe/ui/sidebar/sidebar.js:321 msgid "Toggle sidebar" msgstr "" @@ -28402,7 +28391,7 @@ msgstr "" msgid "Topic" msgstr "" -#: frappe/desk/query_report.py:756 +#: frappe/desk/query_report.py:761 #: frappe/public/js/frappe/views/reports/print_grid.html:50 #: frappe/public/js/frappe/views/reports/query_report.js:1385 #: frappe/public/js/frappe/views/reports/report_view.js:1645 @@ -28557,7 +28546,7 @@ msgstr "" msgid "Translatable" msgstr "" -#: frappe/public/js/frappe/views/reports/query_report.js:2409 +#: frappe/public/js/frappe/views/reports/query_report.js:2414 msgid "Translate Data" msgstr "" @@ -28630,10 +28619,6 @@ msgstr "" msgid "Trigger Method" msgstr "" -#: frappe/public/js/frappe/ui/keyboard.js:196 -msgid "Trigger Primary Action" -msgstr "" - #: frappe/tests/test_translate.py:55 msgid "Trigger caching" msgstr "" @@ -28643,6 +28628,10 @@ msgstr "" msgid "Trigger on valid methods like \"before_insert\", \"after_update\", etc (will depend on the DocType selected)" msgstr "" +#: frappe/public/js/frappe/ui/keyboard.js:207 +msgid "Trigger primary action" +msgstr "" + #: frappe/custom/doctype/customize_form/customize_form.js:153 msgid "Trim Table" msgstr "" @@ -28825,7 +28814,7 @@ msgstr "" msgid "URL for documentation or help" msgstr "" -#: frappe/core/doctype/file/file.py:305 +#: frappe/core/doctype/file/file.py:308 msgid "URL must start with http:// or https://" msgstr "" @@ -28896,7 +28885,7 @@ msgstr "" msgid "UUID" msgstr "" -#: frappe/desk/form/document_follow.py:85 +#: frappe/desk/form/document_follow.py:88 msgid "Un-following document {0}" msgstr "" @@ -28928,7 +28917,7 @@ msgstr "" msgid "Unable to update event" msgstr "" -#: frappe/core/doctype/file/file.py:560 +#: frappe/core/doctype/file/file.py:563 msgid "Unable to write file format for {0}" msgstr "" @@ -28955,7 +28944,7 @@ msgid "Undo last action" msgstr "" #: frappe/public/js/frappe/form/templates/form_sidebar.html:154 -#: frappe/public/js/frappe/form/toolbar.js:950 +#: frappe/public/js/frappe/form/toolbar.js:951 msgid "Unfollow" msgstr "" @@ -29028,7 +29017,7 @@ msgstr "" msgid "Unread Notification Sent" msgstr "" -#: frappe/utils/safe_exec.py:498 +#: frappe/utils/safe_exec.py:496 frappe/utils/safe_exec.py:509 msgid "Unsafe SQL query" msgstr "" @@ -29067,7 +29056,7 @@ msgstr "" msgid "Unsubscribed" msgstr "" -#: frappe/desk/query_report.py:447 +#: frappe/desk/query_report.py:452 msgid "Unsupported file format: {0}" msgstr "" @@ -29204,7 +29193,7 @@ msgstr "" msgid "Updated successfully" msgstr "" -#: frappe/utils/response.py:342 +#: frappe/utils/response.py:350 msgid "Updating" msgstr "" @@ -29751,6 +29740,7 @@ msgid "Username {0} already exists" msgstr "" #. Label of the users (Table MultiSelect) field in DocType 'Assignment Rule' +#. Label of the weighted_users (Table) field in DocType 'Assignment Rule' #. Name of a Workspace #. Label of the users_section (Section Break) field in DocType 'System Health #. Report' @@ -29886,11 +29876,11 @@ msgstr "" msgid "Value To Be Set" msgstr "" -#: frappe/model/base_document.py:830 +#: frappe/model/base_document.py:833 msgid "Value Too Long" msgstr "" -#: frappe/model/base_document.py:1186 frappe/model/document.py:880 +#: frappe/model/base_document.py:1189 frappe/model/document.py:880 msgid "Value cannot be changed for {0}" msgstr "" @@ -29941,7 +29931,7 @@ msgstr "" msgid "Value to set when this workflow state is applied. Use plain text (e.g. Approved) or an expression if “Evaluate as Expression” is enabled." msgstr "" -#: frappe/model/base_document.py:1256 +#: frappe/model/base_document.py:1259 msgid "Value too big" msgstr "" @@ -30050,7 +30040,7 @@ msgstr "" msgid "View File" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:258 +#: frappe/public/js/frappe/ui/notifications/notifications.js:260 msgid "View Full Log" msgstr "" @@ -30163,7 +30153,7 @@ msgstr "" msgid "Visibility" msgstr "" -#: frappe/public/js/frappe/form/templates/timeline_message_box.html:41 +#: frappe/public/js/frappe/form/templates/timeline_message_box.html:42 msgid "Visible to website/portal users." msgstr "" @@ -30598,6 +30588,16 @@ msgstr "" msgid "Weekly Long" msgstr "" +#. Label of the weight (Int) field in DocType 'Assignment Rule User' +#: frappe/automation/doctype/assignment_rule_user/assignment_rule_user.json +msgid "Weight" +msgstr "" + +#. Option for the 'Rule' (Select) field in DocType 'Assignment Rule' +#: frappe/automation/doctype/assignment_rule/assignment_rule.json +msgid "Weighted Distribution" +msgstr "" + #: frappe/desk/page/setup_wizard/setup_wizard.js:403 msgid "Welcome" msgstr "" @@ -30636,7 +30636,7 @@ msgstr "" msgid "Welcome to {0}" msgstr "" -#: frappe/public/js/frappe/ui/notifications/notifications.js:76 +#: frappe/public/js/frappe/ui/notifications/notifications.js:78 msgid "What's New" msgstr "" @@ -30972,7 +30972,7 @@ msgstr "" msgid "Write" msgstr "" -#: frappe/model/base_document.py:1082 +#: frappe/model/base_document.py:1085 msgid "Wrong Fetch From value" msgstr "" @@ -31132,7 +31132,7 @@ msgstr "" msgid "You are not allowed to create columns" msgstr "" -#: frappe/core/doctype/report/report.py:103 +#: frappe/core/doctype/report/report.py:102 msgid "You are not allowed to delete Standard Report" msgstr "" @@ -31144,13 +31144,13 @@ msgstr "" msgid "You are not allowed to delete a standard Website Theme" msgstr "" -#: frappe/core/doctype/report/report.py:414 +#: frappe/core/doctype/report/report.py:436 msgid "You are not allowed to edit the report." msgstr "" #: frappe/core/doctype/data_import/exporter.py:121 #: frappe/core/doctype/data_import/exporter.py:125 -#: frappe/desk/reportview.py:448 frappe/desk/reportview.py:451 +#: frappe/desk/reportview.py:455 frappe/desk/reportview.py:458 #: frappe/permissions.py:651 msgid "You are not allowed to export {} doctype" msgstr "" @@ -31167,7 +31167,7 @@ msgstr "" msgid "You are not allowed to update attendance for another user." msgstr "" -#: frappe/website/doctype/web_form/web_form.py:648 +#: frappe/website/doctype/web_form/web_form.py:653 msgid "You are not allowed to update this Web Form Document" msgstr "" @@ -31248,7 +31248,7 @@ msgstr "" msgid "You can disable this {0} instead of deleting it." msgstr "" -#: frappe/core/doctype/file/file.py:832 +#: frappe/core/doctype/file/file.py:835 msgid "You can increase the limit from System Settings." msgstr "" @@ -31260,7 +31260,7 @@ msgstr "" msgid "You can only edit your own replies." msgstr "" -#: frappe/desk/form/document_follow.py:56 +#: frappe/desk/form/document_follow.py:59 msgid "You can only follow documents for yourself." msgstr "" @@ -31272,7 +31272,7 @@ msgstr "" msgid "You can only print upto {0} documents at a time" msgstr "" -#: frappe/desk/form/document_follow.py:75 +#: frappe/desk/form/document_follow.py:78 msgid "You can only unfollow documents for yourself." msgstr "" @@ -31294,7 +31294,7 @@ msgstr "" msgid "You can set a high value here if multiple users will be logging in from the same network." msgstr "" -#: frappe/desk/query_report.py:401 +#: frappe/desk/query_report.py:406 msgid "You can try changing the filters of your report." msgstr "" @@ -31328,7 +31328,7 @@ msgstr "" msgid "You cannot create a dashboard chart from single DocTypes" msgstr "" -#: frappe/share.py:241 +#: frappe/share.py:243 msgid "You cannot share `{0}` on {1} `{2}` as you do not have `{0}` permission on `{1}`" msgstr "" @@ -31386,11 +31386,11 @@ msgstr "" msgid "You do not have permission to access field: {0}" msgstr "" -#: frappe/core/doctype/file/file.py:229 +#: frappe/core/doctype/file/file.py:232 msgid "You do not have permission to access this file" msgstr "" -#: frappe/desk/query_report.py:1106 +#: frappe/desk/query_report.py:1111 msgid "You do not have permission to access {0}: {1}." msgstr "" @@ -31402,14 +31402,22 @@ msgstr "" msgid "You don't have access to Report: {0}" msgstr "" -#: frappe/website/doctype/web_form/web_form.py:855 +#: frappe/website/doctype/web_form/web_form.py:878 msgid "You don't have permission to access the {0} DocType." msgstr "" -#: frappe/utils/response.py:291 frappe/utils/response.py:295 +#: frappe/utils/response.py:299 frappe/utils/response.py:303 msgid "You don't have permission to access this file" msgstr "" +#: frappe/desk/reportview.py:337 +msgid "You don't have permission to create Report records." +msgstr "" + +#: frappe/desk/reportview.py:340 +msgid "You don't have permission to create report for {0}" +msgstr "" + #: frappe/desk/query_report.py:51 msgid "You don't have permission to get a report on: {0}" msgstr "" @@ -31466,11 +31474,11 @@ msgstr "" msgid "You must add atleast one link." msgstr "" -#: frappe/website/doctype/web_form/web_form.py:851 +#: frappe/website/doctype/web_form/web_form.py:874 msgid "You must be logged in to use this form." msgstr "" -#: frappe/website/doctype/web_form/web_form.py:692 +#: frappe/website/doctype/web_form/web_form.py:697 msgid "You must login to submit this form" msgstr "" @@ -31495,7 +31503,7 @@ msgstr "" msgid "You need to be in developer mode to edit a Standard Web Form" msgstr "" -#: frappe/utils/response.py:280 +#: frappe/utils/response.py:281 msgid "You need to be logged in and have System Manager Role to be able to access backups." msgstr "" @@ -31523,7 +31531,7 @@ msgstr "" msgid "You need to have \"Share\" permission" msgstr "" -#: frappe/utils/print_format.py:322 +#: frappe/utils/print_format.py:326 msgid "You need to install pycups to use this feature!" msgstr "" @@ -31705,7 +31713,7 @@ msgstr "" msgid "Your query has been received. We will reply back shortly. If you have any additional information, please reply to this mail." msgstr "" -#: frappe/desk/query_report.py:352 frappe/desk/reportview.py:399 +#: frappe/desk/query_report.py:357 frappe/desk/reportview.py:406 msgid "Your report is being generated in the background. You will receive an email on {0} with a download link once it is ready." msgstr "" @@ -31805,7 +31813,7 @@ msgstr "" msgid "clear" msgstr "" -#: frappe/public/js/frappe/form/templates/timeline_message_box.html:34 +#: frappe/public/js/frappe/form/templates/timeline_message_box.html:35 msgid "commented" msgstr "" @@ -31884,20 +31892,12 @@ msgstr "" msgid "descending" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:229 -msgid "document type..., e.g. customer" -msgstr "" - #. Description of the 'Email Account Name' (Data) field in DocType 'Email #. Account' #: frappe/email/doctype/email_account/email_account.json msgid "e.g. \"Support\", \"Sales\", \"Jerry Yang\"" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:234 -msgid "e.g. (55 + 434) / 4" -msgstr "" - #. Description of the 'Incoming Server' (Data) field in DocType 'Email Account' #. Description of the 'Incoming Server' (Data) field in DocType 'Email Domain' #: frappe/email/doctype/email_account/email_account.json @@ -32041,7 +32041,7 @@ msgstr "" msgid "just now" msgstr "" -#: frappe/desk/desktop.py:251 frappe/desk/query_report.py:301 +#: frappe/desk/desktop.py:251 frappe/desk/query_report.py:306 msgid "label" msgstr "" @@ -32095,18 +32095,10 @@ msgstr "" msgid "mm/dd/yyyy" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:232 -msgid "module name..." -msgstr "" - #: frappe/public/js/frappe/ui/toolbar/search_utils.js:171 msgid "new" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:228 -msgid "new type of document" -msgstr "" - #. Label of the no_failed (Int) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "no failed attempts" @@ -32326,14 +32318,6 @@ msgstr "" msgid "submit" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:231 -msgid "tag name..., e.g. #tag" -msgstr "" - -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:230 -msgid "text in document type" -msgstr "" - #: frappe/public/js/frappe/form/controls/data.js:36 msgid "this form" msgstr "" @@ -32381,7 +32365,7 @@ msgstr "" msgid "version_table" msgstr "" -#: frappe/automation/doctype/assignment_rule/assignment_rule.py:382 +#: frappe/automation/doctype/assignment_rule/assignment_rule.py:411 msgid "via Assignment Rule" msgstr "" @@ -32465,7 +32449,7 @@ msgstr "" msgid "yyyy-mm-dd" msgstr "" -#: frappe/desk/doctype/event/event.js:87 +#: frappe/desk/doctype/event/event.js:85 #: frappe/public/js/frappe/form/footer/form_timeline.js:547 msgid "{0}" msgstr "" @@ -32491,8 +32475,8 @@ msgstr "" msgid "{0} ({1}) - {2}%" msgstr "" -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:428 -#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:432 +#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:407 +#: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:411 msgid "{0} = {1}" msgstr "" @@ -32549,7 +32533,7 @@ msgstr "" msgid "{0} Name" msgstr "" -#: frappe/model/base_document.py:1286 +#: frappe/model/base_document.py:1289 msgid "{0} Not allowed to change {1} after submission from {2} to {3}" msgstr "" @@ -32760,7 +32744,7 @@ msgstr "" msgid "{0} is a mandatory field" msgstr "" -#: frappe/core/doctype/file/file.py:640 +#: frappe/core/doctype/file/file.py:643 msgid "{0} is a not a valid zip file" msgstr "" @@ -32897,7 +32881,7 @@ msgstr "" msgid "{0} is not a valid report format. Report format should one of the following {1}" msgstr "" -#: frappe/core/doctype/file/file.py:620 +#: frappe/core/doctype/file/file.py:623 msgid "{0} is not a zip file" msgstr "" @@ -33023,11 +33007,11 @@ msgstr "" msgid "{0} must be one of {1}" msgstr "" -#: frappe/model/base_document.py:1004 +#: frappe/model/base_document.py:1007 msgid "{0} must be set first" msgstr "" -#: frappe/model/base_document.py:859 +#: frappe/model/base_document.py:862 msgid "{0} must be unique" msgstr "" @@ -33047,7 +33031,7 @@ msgstr "" msgid "{0} not allowed to be renamed" msgstr "" -#: frappe/core/doctype/report/report.py:463 +#: frappe/core/doctype/report/report.py:485 #: frappe/public/js/frappe/list/list_view.js:1280 msgid "{0} of {1}" msgstr "" @@ -33132,7 +33116,7 @@ msgctxt "User added rows to child table" msgid "{0} rows to {1}" msgstr "" -#: frappe/desk/query_report.py:838 +#: frappe/desk/query_report.py:843 msgid "{0} saved successfully" msgstr "" @@ -33140,7 +33124,7 @@ msgstr "" msgid "{0} self assigned this task: {1}" msgstr "" -#: frappe/share.py:257 +#: frappe/share.py:259 msgid "{0} shared a document {1} {2} with you" msgstr "" @@ -33224,15 +33208,15 @@ msgstr "" msgid "{0} {1} added" msgstr "" -#: frappe/public/js/frappe/utils/dashboard_utils.js:269 +#: frappe/public/js/frappe/utils/dashboard_utils.js:282 msgid "{0} {1} added to Dashboard {2}" msgstr "" -#: frappe/model/base_document.py:778 frappe/model/rename_doc.py:110 +#: frappe/model/base_document.py:781 frappe/model/rename_doc.py:110 msgid "{0} {1} already exists" msgstr "" -#: frappe/model/base_document.py:1115 +#: frappe/model/base_document.py:1118 msgid "{0} {1} cannot be \"{2}\". It should be one of \"{3}\"" msgstr "" @@ -33256,7 +33240,7 @@ msgstr "" msgid "{0} {1}: Submitted Record cannot be deleted. You must {2} Cancel {3} it first." msgstr "" -#: frappe/model/base_document.py:1247 +#: frappe/model/base_document.py:1250 msgid "{0}, Row {1}" msgstr "" @@ -33269,7 +33253,7 @@ msgstr "" msgid "{0}/{1} complete | Please leave this tab open until completion." msgstr "" -#: frappe/model/base_document.py:1252 +#: frappe/model/base_document.py:1255 msgid "{0}: '{1}' ({3}) will get truncated, as max characters allowed is {2}" msgstr "" From fd0c9257b043de3caffb1b711cbc48339b9c4f41 Mon Sep 17 00:00:00 2001 From: gajjug004 Date: Wed, 20 May 2026 17:47:15 +0530 Subject: [PATCH 073/261] fix(number_card): show only cards from user's allowed modules in link picker (cherry picked from commit 259ac583f14ac14f2b7c7cfb3f0acfd9b5dd4b05) --- frappe/desk/doctype/number_card/number_card.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index 19b45ea589f5..df9dee66032c 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -221,9 +221,14 @@ def get_cards_for_user(doctype, txt, searchfield, start, page_len, filters): filters=filters, ) + allowed_modules = [module.get("module_name") for module in get_modules_from_all_apps_for_user()] + return ( condition_query.select(numberCard.name, numberCard.label, numberCard.document_type) .where((numberCard.owner == frappe.session.user) | (numberCard.is_public == 1)) + .where( + numberCard.module.isin(allowed_modules) | numberCard.module.isnull() | (numberCard.module == "") + ) .where(Criterion.any(search_conditions)) ).run() From c71281c41f8b7a5bcfdfacceccfab27c8b0a13de Mon Sep 17 00:00:00 2001 From: gajjug004 Date: Wed, 20 May 2026 18:36:41 +0530 Subject: [PATCH 074/261] refactor: replace list comprehension with set comprehension (cherry picked from commit 524d4895f87ee3ccc0280c1911513240dfd81c53) --- frappe/desk/doctype/number_card/number_card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index df9dee66032c..1e14cb8f329d 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -221,7 +221,7 @@ def get_cards_for_user(doctype, txt, searchfield, start, page_len, filters): filters=filters, ) - allowed_modules = [module.get("module_name") for module in get_modules_from_all_apps_for_user()] + allowed_modules = {module.get("module_name") for module in get_modules_from_all_apps_for_user()} return ( condition_query.select(numberCard.name, numberCard.label, numberCard.document_type) From ba99d0636a7d64f0a18903cf571b106f73a8f30f Mon Sep 17 00:00:00 2001 From: gajjug004 Date: Thu, 21 May 2026 15:19:53 +0530 Subject: [PATCH 075/261] test: add test case (cherry picked from commit 0744c9b96013c438abf86c1055eb7531c8f4706b) # Conflicts: # frappe/desk/doctype/number_card/test_number_card.py --- .../doctype/number_card/test_number_card.py | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/frappe/desk/doctype/number_card/test_number_card.py b/frappe/desk/doctype/number_card/test_number_card.py index ee78aeb7f66b..ac87aa30d6d1 100644 --- a/frappe/desk/doctype/number_card/test_number_card.py +++ b/frappe/desk/doctype/number_card/test_number_card.py @@ -1,8 +1,145 @@ # Copyright (c) 2020, Frappe Technologies and Contributors # License: MIT. See LICENSE +<<<<<<< HEAD # import frappe +======= +import frappe +from frappe.desk.doctype.number_card.number_card import get_cards_for_user +>>>>>>> 0744c9b960 (test: add test case) from frappe.tests import IntegrationTestCase class TestNumberCard(IntegrationTestCase): +<<<<<<< HEAD pass +======= + def test_report_card_hidden_when_report_is_not_allowed(self): + user = "test2@example.com" + report_name = "Test Restricted Number Card Report" + card_name = "Test Restricted Report Number Card" + baseline_role = "Desk User" + exclusive_role = "System Manager" + + frappe.set_user("Administrator") + frappe.delete_doc("Number Card", card_name, ignore_missing=True, force=True) + frappe.delete_doc("Report", report_name, ignore_missing=True, force=True) + self.addCleanup(lambda: frappe.delete_doc("Number Card", card_name, ignore_missing=True, force=True)) + self.addCleanup(lambda: frappe.delete_doc("Report", report_name, ignore_missing=True, force=True)) + + user_doc = frappe.get_doc("User", user) + had_baseline_role = baseline_role in frappe.get_roles(user) + if not had_baseline_role: + user_doc.add_roles(baseline_role) + self.addCleanup(lambda: user_doc.remove_roles(baseline_role)) + + had_exclusive_role = exclusive_role in frappe.get_roles(user) + if had_exclusive_role: + user_doc.remove_roles(exclusive_role) + self.addCleanup(lambda: user_doc.add_roles(exclusive_role)) + + report = frappe.get_doc( + { + "doctype": "Report", + "report_name": report_name, + "ref_doctype": "ToDo", + "report_type": "Report Builder", + "is_standard": "No", + "roles": [{"role": exclusive_role}], + } + ).insert(ignore_permissions=True) + + card = frappe.get_doc( + { + "doctype": "Number Card", + "label": card_name, + "type": "Report", + "report_name": report.name, + "function": "Count", + "report_field": "name", + } + ).insert(ignore_permissions=True) + + self.assertFalse(frappe.has_permission("Number Card", doc=card, user=user)) + self.assertNotIn( + card.name, + frappe.get_list("Number Card", filters={"name": card.name}, pluck="name", user=user), + ) + + def test_link_search_hides_cards_from_blocked_modules(self): + user = "test2@example.com" + blocked_module = "Contacts" + blocked_card_name = "Test Blocked Module Card" + allowed_card_name = "Test No Module Card" + + frappe.set_user("Administrator") + + admin_blocked = frappe.get_cached_doc("User", "Administrator").get_blocked_modules() + self.assertNotIn(blocked_module, admin_blocked, f"{blocked_module} globally blocked — test invalid") + + for card_name in (blocked_card_name, allowed_card_name): + frappe.delete_doc("Number Card", card_name, ignore_missing=True, force=True) + self.addCleanup( + lambda c=card_name: frappe.delete_doc("Number Card", c, ignore_missing=True, force=True) + ) + + user_doc = frappe.get_doc("User", user) + if blocked_module not in {d.module for d in user_doc.block_modules}: + user_doc.append("block_modules", {"module": blocked_module}) + user_doc.save(ignore_permissions=True) + frappe.clear_document_cache("User", user) + + def restore_blocks(): + doc = frappe.get_doc("User", user) + doc.block_modules = {d for d in doc.block_modules if d.module != blocked_module} + doc.save(ignore_permissions=True) + frappe.clear_document_cache("User", user) + + self.addCleanup(restore_blocks) + + frappe.get_doc( + { + "doctype": "Number Card", + "label": blocked_card_name, + "type": "Document Type", + "document_type": "ToDo", + "function": "Count", + "is_public": 1, + "module": blocked_module, + } + ).insert(ignore_permissions=True) + + frappe.get_doc( + { + "doctype": "Number Card", + "label": allowed_card_name, + "type": "Document Type", + "document_type": "ToDo", + "function": "Count", + "is_public": 1, + } + ).insert(ignore_permissions=True) + + frappe.set_user(user) + try: + blocked_results = get_cards_for_user( + doctype="Number Card", + txt=blocked_card_name, + searchfield="name", + start=0, + page_len=20, + filters={}, + ) + allowed_results = get_cards_for_user( + doctype="Number Card", + txt=allowed_card_name, + searchfield="name", + start=0, + page_len=20, + filters={}, + ) + finally: + frappe.set_user("Administrator") + + self.assertEqual([row[0] for row in blocked_results], []) + self.assertEqual([row[0] for row in allowed_results], [allowed_card_name]) +>>>>>>> 0744c9b960 (test: add test case) From 25330480b9dfb18c21d66174ef49c86ce79341f8 Mon Sep 17 00:00:00 2001 From: Vibhuti Garachh <157696107+Vibhuti410@users.noreply.github.com> Date: Mon, 25 May 2026 14:51:41 +0530 Subject: [PATCH 076/261] fix: section padding (#39490) (cherry picked from commit d48f0e39673475d5dc5fa812b7a4d08c08155590) --- frappe/public/scss/desk/form.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/public/scss/desk/form.scss b/frappe/public/scss/desk/form.scss index b4642ee32ee5..34a8889ff977 100644 --- a/frappe/public/scss/desk/form.scss +++ b/frappe/public/scss/desk/form.scss @@ -32,7 +32,7 @@ padding: 0 var(--padding-md); .form-section-description { max-width: var(--page-max-width); - margin-left: 0; + margin: auto; margin-bottom: 10px; @include get_textstyle("base", "regular"); color: var(--text-light); @@ -79,6 +79,12 @@ } } +body.full-width { + .form-section-description { + margin-left: 0; + } +} + .std-form-layout { .section-head, .section-body { From 808fd039d15d74806fc5728ef6709b06dbd64353 Mon Sep 17 00:00:00 2001 From: Sudhanshu-Badole Date: Sun, 24 May 2026 11:29:53 +0530 Subject: [PATCH 077/261] fix(web_form): close open child table row form when navigating multi-tab web form (cherry picked from commit 555cc840dc7d944821a913f0d84f3903a075959c) --- frappe/public/js/frappe/web_form/web_form.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/public/js/frappe/web_form/web_form.js b/frappe/public/js/frappe/web_form/web_form.js index 2a60fa310847..29b1b21aa1ec 100644 --- a/frappe/public/js/frappe/web_form/web_form.js +++ b/frappe/public/js/frappe/web_form/web_form.js @@ -271,6 +271,9 @@ export default class WebForm extends frappe.ui.FieldGroup { toggle_section() { if (!this.is_multi_step_form) return; + // close any open child table row form before switching pages + frappe.ui.form?.close_grid_form && frappe.ui.form.close_grid_form(); + this.render_progress_dots(); this.toggle_previous_button(); this.hide_form_pages(); From 2be653c395f9dfc76c3904011aa3758323710742 Mon Sep 17 00:00:00 2001 From: KerollesFathy Date: Wed, 20 May 2026 17:26:01 +0000 Subject: [PATCH 078/261] fix: escape key should close dialog after closing autocomplete dropdown (cherry picked from commit fb4490a9e231510ea1cdf77cc7c92386f5f2c9f1) --- frappe/public/js/frappe/ui/dialog.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 036dc0c1cd05..c0dd65e878d4 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -131,12 +131,28 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { }) .on("keydown", function (e) { if (e.key === "Escape" || e.keyCode === 27) { - // when dialog is open and contains an awesomplete dropdown - do not close the dialog on escape key press - if (me.display && me.$wrapper.find(".awesomplete").length) { + // _awesomplete_was_open is set in the capture-phase listener below, before + // Awesomplete's own keydown handler closes the dropdown and clears aria-expanded. + if (me._awesomplete_was_open) { + me._awesomplete_was_open = false; e.stopImmediatePropagation(); } } }); + + // Runs in capture phase, before Awesomplete's bubble-phase keydown handler on the input, + // so aria-expanded is still "true" when the dropdown is open. + me.$wrapper[0].addEventListener( + "keydown", + function (e) { + if (e.key === "Escape" || e.keyCode === 27) { + me._awesomplete_was_open = + me.display && + !!me.$wrapper.find(".awesomplete input[aria-expanded='true']").length; + } + }, + true // capture phase + ); } set_modal_size() { From 9bcf043d81d5aaf22684e342e2fa33687e23bda8 Mon Sep 17 00:00:00 2001 From: gajjug004 Date: Mon, 25 May 2026 17:54:44 +0530 Subject: [PATCH 079/261] fix: resolve merge conflicts in test_number_card.py --- .../doctype/number_card/test_number_card.py | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/frappe/desk/doctype/number_card/test_number_card.py b/frappe/desk/doctype/number_card/test_number_card.py index ac87aa30d6d1..7e60752415b6 100644 --- a/frappe/desk/doctype/number_card/test_number_card.py +++ b/frappe/desk/doctype/number_card/test_number_card.py @@ -1,70 +1,11 @@ # Copyright (c) 2020, Frappe Technologies and Contributors # License: MIT. See LICENSE -<<<<<<< HEAD -# import frappe -======= import frappe from frappe.desk.doctype.number_card.number_card import get_cards_for_user ->>>>>>> 0744c9b960 (test: add test case) from frappe.tests import IntegrationTestCase class TestNumberCard(IntegrationTestCase): -<<<<<<< HEAD - pass -======= - def test_report_card_hidden_when_report_is_not_allowed(self): - user = "test2@example.com" - report_name = "Test Restricted Number Card Report" - card_name = "Test Restricted Report Number Card" - baseline_role = "Desk User" - exclusive_role = "System Manager" - - frappe.set_user("Administrator") - frappe.delete_doc("Number Card", card_name, ignore_missing=True, force=True) - frappe.delete_doc("Report", report_name, ignore_missing=True, force=True) - self.addCleanup(lambda: frappe.delete_doc("Number Card", card_name, ignore_missing=True, force=True)) - self.addCleanup(lambda: frappe.delete_doc("Report", report_name, ignore_missing=True, force=True)) - - user_doc = frappe.get_doc("User", user) - had_baseline_role = baseline_role in frappe.get_roles(user) - if not had_baseline_role: - user_doc.add_roles(baseline_role) - self.addCleanup(lambda: user_doc.remove_roles(baseline_role)) - - had_exclusive_role = exclusive_role in frappe.get_roles(user) - if had_exclusive_role: - user_doc.remove_roles(exclusive_role) - self.addCleanup(lambda: user_doc.add_roles(exclusive_role)) - - report = frappe.get_doc( - { - "doctype": "Report", - "report_name": report_name, - "ref_doctype": "ToDo", - "report_type": "Report Builder", - "is_standard": "No", - "roles": [{"role": exclusive_role}], - } - ).insert(ignore_permissions=True) - - card = frappe.get_doc( - { - "doctype": "Number Card", - "label": card_name, - "type": "Report", - "report_name": report.name, - "function": "Count", - "report_field": "name", - } - ).insert(ignore_permissions=True) - - self.assertFalse(frappe.has_permission("Number Card", doc=card, user=user)) - self.assertNotIn( - card.name, - frappe.get_list("Number Card", filters={"name": card.name}, pluck="name", user=user), - ) - def test_link_search_hides_cards_from_blocked_modules(self): user = "test2@example.com" blocked_module = "Contacts" @@ -142,4 +83,3 @@ def restore_blocks(): self.assertEqual([row[0] for row in blocked_results], []) self.assertEqual([row[0] for row in allowed_results], [allowed_card_name]) ->>>>>>> 0744c9b960 (test: add test case) From fd43d75181bcb4893cab409e756569d60e917ec2 Mon Sep 17 00:00:00 2001 From: sokumon Date: Sat, 23 May 2026 15:27:29 +0530 Subject: [PATCH 080/261] fix: replace with nicer icon (cherry picked from commit 1f24041f122894c222913afdb2c567062ab345d7) --- frappe/public/js/frappe/form/controls/multiselect_pills.js | 2 +- frappe/public/js/frappe/form/controls/table_multiselect.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/multiselect_pills.js b/frappe/public/js/frappe/form/controls/multiselect_pills.js index 5bc89e3a8c82..748b94b29c8e 100644 --- a/frappe/public/js/frappe/form/controls/multiselect_pills.js +++ b/frappe/public/js/frappe/form/controls/multiselect_pills.js @@ -92,7 +92,7 @@ frappe.ui.form.ControlMultiSelectPills = class ControlMultiSelectPills extends ( return ` `; } diff --git a/frappe/public/js/frappe/form/controls/table_multiselect.js b/frappe/public/js/frappe/form/controls/table_multiselect.js index 44b4ef432544..cb3a577153de 100644 --- a/frappe/public/js/frappe/form/controls/table_multiselect.js +++ b/frappe/public/js/frappe/form/controls/table_multiselect.js @@ -191,7 +191,7 @@ frappe.ui.form.ControlTableMultiSelect = class ControlTableMultiSelect extends ( return ` `; } From 47afaa741b91ba082548333418b847eb7eec4f5d Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 26 May 2026 11:55:40 +0530 Subject: [PATCH 081/261] fix: remove gemoji from bundle (cherry picked from commit 74dfb88968dc9dbb5f29a4d889977f82d6e1930a) --- .../js/frappe/icon_picker/icon_picker.js | 53 ++----------------- frappe/public/js/frappe/utils/utils.js | 21 ++++++-- frappe/public/js/libs.bundle.js | 3 -- package.json | 1 - 4 files changed, 23 insertions(+), 55 deletions(-) diff --git a/frappe/public/js/frappe/icon_picker/icon_picker.js b/frappe/public/js/frappe/icon_picker/icon_picker.js index 15c25c7f8e10..25860bbcf332 100644 --- a/frappe/public/js/frappe/icon_picker/icon_picker.js +++ b/frappe/public/js/frappe/icon_picker/icon_picker.js @@ -35,63 +35,20 @@ class Picker { } } setup_emojis() { - // setup tab this.setup_tab(); - // setup emoji container this.setup_emoji_container(); - // emojis this.emoji_wrapper = this.icon_picker_wrapper.find(".emojis"); - gemoji.forEach((emoji, i) => { - let $icon = $( - `
${gemoji[i].emoji}
` - ); + frappe.utils.get_emojis().forEach((emoji) => { + const $icon = $(`
${emoji}
`); this.emoji_wrapper.append($icon); - const set_values = () => { - this.set_icon(gemoji[i].emoji); - this.update_icon_selected(); - }; $icon.on("click", () => { - set_values(); + this.set_icon(emoji); + this.update_icon_selected(); }); - // $icon.keydown((e) => { - // const key_code = e.keyCode; - // if ([13, 32].includes(key_code)) { - // e.preventDefault(); - // set_values(); - // } - // }); - }); - this.search_input.on("input", (e) => { - e.preventDefault(); - this.filter_emojis(); }); } filter_emojis() { - let value = this.search_input.val(); - let filtered_emoji_names = []; - if (value) { - gemoji.forEach((g) => { - g.tags.forEach((t) => { - if (t.includes(value)) { - filtered_emoji_names.push(g); - } - }); - g.names.forEach((t) => { - if (t.includes(value)) { - filtered_emoji_names.push(g); - } - }); - }); - } - - if (filtered_emoji_names.length == 0) { - this.emoji_wrapper.find(".emoji-wrapper").removeClass("hidden"); - } else { - this.emoji_wrapper.find(".emoji-wrapper").addClass("hidden"); - filtered_emoji_names.forEach((g) => { - this.emoji_wrapper.find(`.emoji-wrapper[id*='${g.emoji}']`).removeClass("hidden"); - }); - } + this.emoji_wrapper.find(".emoji-wrapper").removeClass("hidden"); } setup_emoji_container() { this.icon_picker_wrapper.find(".icon-section") diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 5d9827d375d5..a7ad2aadfd0a 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1433,9 +1433,24 @@ Object.assign(frappe.utils, { return ``; }, - is_emoji(emoji_name) { - let emojiList = gemoji.map((emoji) => emoji.emoji); - return emojiList.includes(emoji_name); + is_emoji(str) { + return /^\p{Extended_Pictographic}(‍\p{Extended_Pictographic}|️|⃣)*$/u.test(str); + }, + + get_emojis() { + const ranges = [ + [0x1f600, 0x1f64f], // Emoticons + [0x1f300, 0x1f5ff], // Misc Symbols and Pictographs + [0x1f680, 0x1f6ff], // Transport and Map + [0x1f900, 0x1f9ff], // Supplemental Symbols and Pictographs + [0x1fa00, 0x1fa6f], // Chess Symbols + [0x1fa70, 0x1faff], // Symbols and Pictographs Extended-A + [0x2600, 0x26ff], // Misc Symbols + [0x2700, 0x27bf], // Dingbats + ]; + return ranges.flatMap(([start, end]) => + Array.from({ length: end - start + 1 }, (_, i) => String.fromCodePoint(start + i)) + ); }, get_desktop_icon(icon_name, variant) { diff --git a/frappe/public/js/libs.bundle.js b/frappe/public/js/libs.bundle.js index 0e1d5df2efe4..368b33b22a32 100644 --- a/frappe/public/js/libs.bundle.js +++ b/frappe/public/js/libs.bundle.js @@ -5,11 +5,8 @@ import "../js/lib/leaflet_easy_button/easy-button.js"; import "../js/lib/leaflet_draw/leaflet.draw.js"; import "../js/lib/leaflet_control_locate/L.Control.Locate.js"; import Sortable from "sortablejs"; -import { gemoji } from "gemoji"; - window.SetVueGlobals = (app) => { app.config.globalProperties.__ = window.__; app.config.globalProperties.frappe = window.frappe; }; window.Sortable = Sortable; -window.gemoji = gemoji; diff --git a/package.json b/package.json index ff869962c23e..1ca3a0090fe5 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "frappe-datatable": "1.19.0", "frappe-gantt": "^0.6.0", "frappe-quill-image-resize": "^3.0.9", - "gemoji": "^8.1.0", "highlight.js": "^10.4.1", "html5-qrcode": "^2.3.8", "jquery": "3.7.0", From 302932ce1b46d9a97fc19bc6ac7a96debbcc414f Mon Sep 17 00:00:00 2001 From: sokumon Date: Tue, 26 May 2026 12:01:53 +0530 Subject: [PATCH 082/261] fix: don't show selected icon for emoji (cherry picked from commit 2c9c6fd4bcbffe9e7c8dd1dcf15ae1495bfa04c0) --- frappe/public/js/frappe/form/formatters.js | 5 +++++ yarn.lock | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 04da43fee146..28148030c635 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -398,6 +398,11 @@ frappe.form.formatters = { Icon: (value) => { if (!value) return ""; let escaped_value = frappe.utils.escape_html(value); + if (frappe.utils.is_emoji(value)) { + return `
+ ${escaped_value} +
`; + } return `
${frappe.utils.icon(escaped_value, "md")}
${escaped_value} diff --git a/yarn.lock b/yarn.lock index 303baa1f5671..f52fc1a4ac0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1489,11 +1489,6 @@ functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -gemoji@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/gemoji/-/gemoji-8.1.0.tgz#3d47a26e569c51efa95198822a6f483d7a7ae600" - integrity sha512-HA4Gx59dw2+tn+UAa7XEV4ufUKI4fH1KgcbenVA9YKSj1QJTT0xh5Mwv5HMFNN3l2OtUe3ZIfuRwSyZS5pLIWw== - generic-names@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-1.0.3.tgz#2d786a121aee508876796939e8e3bff836c20917" From c1d755326cea28f3d65199605db61d83424576da Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Tue, 26 May 2026 14:31:47 +0530 Subject: [PATCH 083/261] fix(page): restrict folder deletion to developer mode (cherry picked from commit 2c09ebc5cc404cd2dede24bf30ac18f7ed595f3e) --- frappe/core/doctype/page/page.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py index 23dea8ecc67a..f7bef48732d3 100644 --- a/frappe/core/doctype/page/page.py +++ b/frappe/core/doctype/page/page.py @@ -110,7 +110,8 @@ def on_trash(self): frappe.throw(_("Deletion of this document is only permitted in developer mode.")) delete_custom_role("page", self.name) - frappe.db.after_commit(self.delete_folder_with_contents) + if frappe.conf.developer_mode: + frappe.db.after_commit(self.delete_folder_with_contents) def delete_folder_with_contents(self): try: From 9eb3c3f5d7e48ebc8723b52abb3e9164ed8aa223 Mon Sep 17 00:00:00 2001 From: Vibhuti Garachh <157696107+Vibhuti410@users.noreply.github.com> Date: Tue, 26 May 2026 15:33:09 +0530 Subject: [PATCH 084/261] fix: replace change link with clickable image and camera hint on hover (#39513) * feat: replace change link with clickable image and camera hint on hover (cherry picked from commit e10caa86251608887520300593ff45a46f1a4ab5) --- .../js/frappe/form/sidebar/user_image.js | 55 ++++++------------- .../frappe/form/templates/form_sidebar.html | 11 ++-- frappe/public/scss/desk/form_sidebar.scss | 46 ++++++++++++---- 3 files changed, 59 insertions(+), 53 deletions(-) diff --git a/frappe/public/js/frappe/form/sidebar/user_image.js b/frappe/public/js/frappe/form/sidebar/user_image.js index b022b157b940..c1ef7b76ce2d 100644 --- a/frappe/public/js/frappe/form/sidebar/user_image.js +++ b/frappe/public/js/frappe/form/sidebar/user_image.js @@ -17,12 +17,9 @@ frappe.ui.form.set_user_image = function (frm) { image = window.cordova && image.indexOf("http") === -1 ? frappe.base_url + image : image; image_section.find(".sidebar-image").attr("src", image).removeClass("hide"); - image_section.find(".sidebar-standard-image").addClass("hide"); - title_image.css("background-image", `url("${image}")`).html(""); - - image_actions.find(".sidebar-image-change, .sidebar-image-remove").show(); + image_actions.find(".sidebar-image-remove").show(); } else { image_section.find(".sidebar-image").attr("src", null).addClass("hide"); @@ -35,8 +32,6 @@ frappe.ui.form.set_user_image = function (frm) { .html(frappe.get_abbr(title)); title_image.css("background-image", "").html(frappe.get_abbr(title)); - - image_actions.find(".sidebar-image-change").show(); image_actions.find(".sidebar-image-remove").hide(); } }; @@ -50,40 +45,26 @@ frappe.ui.form.setup_user_image_event = function (frm) { } if (frm.meta.image_field && !frm.fields_dict[frm.meta.image_field].df.read_only) { - frm.sidebar.image_wrapper.on("click", ":not(.sidebar-image-actions)", (e) => { - let $target = $(e.currentTarget); - if ($target.is("a.dropdown-toggle, .dropdown")) { + // clicking anywhere on the image wrapper triggers upload + frm.sidebar.image_wrapper.on("click", function (e) { + if ($(e.target).closest(".sidebar-image-remove").length) { return; } - let dropdown = frm.sidebar.image_wrapper.find(".sidebar-image-actions .dropdown"); - dropdown.toggleClass("open"); - e.stopPropagation(); + var field = frm.get_field(frm.meta.image_field); + if (!field.$input) { + field.make_input(); + } + field.$input.trigger("attach_doc_image"); + frm.page.close_sidebar?.(); }); } - // bind click on image_wrapper - frm.sidebar.image_wrapper.on( - "click", - ".sidebar-image-change, .sidebar-image-remove", - function (e) { - let $target = $(e.currentTarget); - var field = frm.get_field(frm.meta.image_field); - if ($target.is(".sidebar-image-change")) { - if (!field.$input) { - field.make_input(); - } - field.$input.trigger("attach_doc_image"); - // close sidebar - frm.page.close_sidebar?.(); - } else { - /// on remove event for a sidebar image wrapper remove attach file. - frm.attachments.remove_attachment_by_filename( - frm.doc[frm.meta.image_field], - function () { - field.set_value("").then(() => frm.save()); - } - ); - } - } - ); + // remove button + frm.sidebar.image_wrapper.on("click", ".sidebar-image-remove", function (e) { + e.stopPropagation(); + var field = frm.get_field(frm.meta.image_field); + frm.attachments.remove_attachment_by_filename(frm.doc[frm.meta.image_field], function () { + field.set_value("").then(() => frm.save()); + }); + }); }; diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 9886638ad948..0403e9c15c3e 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -6,13 +6,12 @@
{% if can_write %} diff --git a/frappe/public/scss/desk/form_sidebar.scss b/frappe/public/scss/desk/form_sidebar.scss index d590deac4969..e5901105d092 100644 --- a/frappe/public/scss/desk/form_sidebar.scss +++ b/frappe/public/scss/desk/form_sidebar.scss @@ -172,6 +172,7 @@ position: relative; width: fit-content; height: fit-content; + cursor: pointer; } .sidebar-image, @@ -194,12 +195,42 @@ .sidebar-image-actions { display: none; position: absolute; - top: 50%; - right: 0; - left: 0; - transform: translateY(-50%); - text-align: center; + inset: 0; z-index: 1; + + .sidebar-image-upload-hint { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 50%; + display: flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--gray-900) 15%, transparent); + border-radius: 0 0 var(--border-radius-lg) var(--border-radius-lg); + color: var(--text-color); + line-height: 0; + pointer-events: none; + } + + .sidebar-image-remove { + position: absolute; + top: 0; + right: 0; + transform: translate(50%, -50%); + background: var(--bg-color); + border-radius: var(--border-radius-full); + padding: 2px; + line-height: 0; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + background: var(--subtle-fg); + } + } } // TODO: find better fix .sidebar-standard-image { @@ -208,11 +239,6 @@ padding: 50% 0; } } - - .form-details { - display: flex; - align-items: center; - } } .sidebar-meta-details { .form-name-copy { From a6b41ac0c2a21b61bc159728912e3fa611fd86e1 Mon Sep 17 00:00:00 2001 From: KerollesFathy Date: Sun, 24 May 2026 12:15:03 +0000 Subject: [PATCH 085/261] fix(sidebar): hide promotional banner title when sidebar is collapsed (cherry picked from commit a017cac23ee2252945a5c2290a580363976bedf7) --- frappe/public/js/frappe/ui/sidebar/sidebar.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.js b/frappe/public/js/frappe/ui/sidebar/sidebar.js index 5c2f1a0f0ba7..c7f24ba09cfb 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.js @@ -160,8 +160,8 @@ frappe.ui.Sidebar = class Sidebar { this.promotional_banners.forEach((banner) => { let banner_html = $(` - - ${banner.title} + + ${banner.title} `); @@ -570,6 +570,7 @@ frappe.ui.Sidebar = class Sidebar { $('[data-toggle="tooltip"]').tooltip("dispose"); this.wrapper.find(".avatar-name-email").show(); this.wrapper.find(".onboarding-sidebar span").show(); + this.wrapper.find(".promotional-banner-title").show(); } else { this.wrapper.removeClass("expanded"); direction = is_rtl ? "right" : "left"; @@ -580,6 +581,7 @@ frappe.ui.Sidebar = class Sidebar { }); this.wrapper.find(".avatar-name-email").hide(); this.wrapper.find(".onboarding-sidebar span").hide(); + this.wrapper.find(".promotional-banner-title").hide(); } localStorage.setItem("sidebar-expanded", this.sidebar_expanded); From 6f3d80bfa62cea1cbe0e817a6a40867a48ba28c6 Mon Sep 17 00:00:00 2001 From: AarDG10 Date: Tue, 26 May 2026 12:25:39 +0530 Subject: [PATCH 086/261] fix(workspace): add check for public ws. too Public workspaces should ideally be editable only by Workspace Managers. (cherry picked from commit 66bf54646a91637ff2c50223d25f240753dd3a67) --- frappe/desk/doctype/workspace/workspace.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index 77e505612a6f..b42841d84873 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -358,9 +358,11 @@ def update_page(name, title, icon, indicator_color, parent, public): public = frappe.parse_json(public) doc = frappe.get_doc("Workspace", name) - if not doc.get("public") and doc.get("for_user") != frappe.session.user and not is_workspace_manager(): + if doc.get("public") and not is_workspace_manager(): + frappe.throw(_("Need Workspace Manager role to edit public workspaces.")) + elif not doc.get("public") and doc.get("for_user") != frappe.session.user and not is_workspace_manager(): frappe.throw( - _("Need Workspace Manager role to edit private workspace of other users"), + _("Need Workspace Manager role to edit private workspace of other users."), frappe.PermissionError, ) From 531a9d52ded43123dc24d848e2289d80dac080c3 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 19 May 2026 12:11:34 +0530 Subject: [PATCH 087/261] fix: append existing link filters instead of overwriting (#39231) * fix: append existing link filters instead of overwriting * chore: rename variable (cherry picked from commit 3cac389b0cc4eff4e7945a9445587d762f15fb49) --- frappe/public/js/frappe/form/controls/link.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 733749f20972..74d2d3d2d0b7 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -850,9 +850,13 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat } apply_link_field_filters() { - let link_filters = JSON.parse(this.df.link_filters); - let filters = this.parse_filters(link_filters); + let filters = this.parse_filters(JSON.parse(this.df.link_filters)); // take filters from the link field and add to the query + const query_filters = this.get_query?.()?.filters || {}; + if (query_filters) { + filters = { ...filters, ...query_filters }; + } + this.get_query = function () { return { filters, From 148ffd95588a71d4e29f31c102b10b4a9c947e0f Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:07:57 +0530 Subject: [PATCH 088/261] perf(form): speed up mandatory check, dedupe row errors (cherry picked from commit e625ce0d5220e42aebc868517ec4fa206e113655) --- frappe/public/js/frappe/form/save.js | 83 +++++++++++++++++++--------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index 5640514eec3f..8c6b2e02274e 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -123,25 +123,25 @@ frappe.ui.form.check_mandatory = function (frm) { if (frm.doc.docstatus == 2) return true; // don't check for cancel + const table_errors = {}; + $.each(frappe.model.get_all_docs(frm.doc), function (i, doc) { var error_fields = []; var folded = false; + const fields_dict = frappe.meta.get_docfield_copy(doc.doctype, doc.name) || {}; $.each(frappe.meta.docfield_list[doc.doctype] || [], function (i, docfield) { if (docfield.fieldname) { - const df = frappe.meta.get_docfield(doc.doctype, docfield.fieldname, doc.name); + const df = fields_dict[docfield.fieldname]; + if (!df) return; - // skip fields that don't hold data - if ( - ["Section Break", "Column Break", "Tab Break", "HTML", "Heading"].includes( - df.fieldtype - ) - ) { + if (!df.reqd && !df.mandatory_depends_on && df.fieldtype !== "Fold") { return; } if (df.fieldtype === "Fold") { folded = frm.layout.folded; + return; } if ( @@ -170,31 +170,62 @@ frappe.ui.form.check_mandatory = function (frm) { if (error_fields.length) { let meta = frappe.get_meta(doc.doctype); - let message; if (meta.istable) { - const table_field = frappe.meta.docfield_map[doc.parenttype][doc.parentfield]; - - const table_label = __( - table_field.label || frappe.unscrub(table_field.fieldname) - ).bold(); - - message = __("Mandatory fields required in table {0}, Row {1}", [ - table_label, - doc.idx, - ]); + const parentfield = doc.parentfield; + if (!table_errors[parentfield]) { + const table_field = frappe.meta.docfield_map[doc.parenttype][parentfield]; + const table_label = __( + table_field.label || frappe.unscrub(table_field.fieldname) + ).bold(); + table_errors[parentfield] = { + label: table_label, + fields: {}, + total_rows: (frm.doc[parentfield] || []).length, + }; + } + error_fields.forEach(function (field_label) { + if (!table_errors[parentfield].fields[field_label]) { + table_errors[parentfield].fields[field_label] = []; + } + table_errors[parentfield].fields[field_label].push(doc.idx); + }); } else { - message = __("Mandatory fields required in {0}", [__(doc.doctype)]); + const message = + __("Mandatory fields required in {0}", [__(doc.doctype)]) + + "

  • " + + error_fields.join("
  • ") + + "
"; + frappe.msgprint({ + message: message, + indicator: "red", + title: __("Missing Fields"), + }); + frm.refresh(); } - message = message + "

  • " + error_fields.join("
  • ") + "
"; - frappe.msgprint({ - message: message, - indicator: "red", - title: __("Missing Fields"), - }); - frm.refresh(); } }); + Object.values(table_errors).forEach(function (te) { + const lines = Object.entries(te.fields).map(function (entry) { + const rows = entry[1]; + const display = + rows.length === te.total_rows + ? __("all {0} rows", [te.total_rows]) + : __("Row {0}", [rows.join(", ")]); + return entry[0] + ": " + display; + }); + frappe.msgprint({ + message: + __("Mandatory fields required in table {0}", [te.label]) + + "

  • " + + lines.join("
  • ") + + "
", + indicator: "red", + title: __("Missing Fields"), + }); + frm.refresh(); + }); + return !has_errors; function is_docfield_mandatory(doc, df) { From f7a17b9da28a8301edf67fbc0a918e08d4f0a6f7 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Sun, 10 May 2026 17:46:51 +0530 Subject: [PATCH 089/261] refactor(form): rewrite missing-fields dialog as sentences with row ranges (cherry picked from commit 4fd6b08c21f75db37872b03fedc05b53b73bea70) --- frappe/public/js/frappe/form/save.js | 80 +++++++++++++++++++++------- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index 8c6b2e02274e..4e96e643d842 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -123,6 +123,8 @@ frappe.ui.form.check_mandatory = function (frm) { if (frm.doc.docstatus == 2) return true; // don't check for cancel + const ROW_LIMIT = 10; + const parent_errors = []; const table_errors = {}; $.each(frappe.model.get_all_docs(frm.doc), function (i, doc) { @@ -190,41 +192,79 @@ frappe.ui.form.check_mandatory = function (frm) { table_errors[parentfield].fields[field_label].push(doc.idx); }); } else { - const message = - __("Mandatory fields required in {0}", [__(doc.doctype)]) + - "

  • " + - error_fields.join("
  • ") + - "
"; - frappe.msgprint({ - message: message, - indicator: "red", - title: __("Missing Fields"), + error_fields.forEach(function (field_label) { + parent_errors.push(__("{0} is required.", [field_label.bold()])); }); - frm.refresh(); } } }); + const lines = [...parent_errors]; Object.values(table_errors).forEach(function (te) { - const lines = Object.entries(te.fields).map(function (entry) { - const rows = entry[1]; - const display = - rows.length === te.total_rows - ? __("all {0} rows", [te.total_rows]) - : __("Row {0}", [rows.join(", ")]); - return entry[0] + ": " + display; + Object.entries(te.fields).forEach(function (entry) { + const field_label = entry[0]; + const rows = entry[1].sort((a, b) => a - b); + + const ranges = []; + let start = rows[0]; + let prev = rows[0]; + for (let i = 1; i < rows.length; i++) { + if (rows[i] === prev + 1) { + prev = rows[i]; + } else { + ranges.push(start === prev ? `${start}` : `${start}-${prev}`); + start = prev = rows[i]; + } + } + ranges.push(start === prev ? `${start}` : `${start}-${prev}`); + + if (rows.length === te.total_rows) { + lines.push( + __("In {0}, {1} is required in every row.", [ + te.label, + field_label.bold(), + ]) + ); + } else if (rows.length === 1) { + lines.push( + __("In {0}, {1} is required in row {2}.", [ + te.label, + field_label.bold(), + rows[0], + ]) + ); + } else if (ranges.length <= ROW_LIMIT) { + lines.push( + __("In {0}, {1} is required in rows {2}.", [ + te.label, + field_label.bold(), + frappe.utils.comma_and(ranges), + ]) + ); + } else { + lines.push( + __("In {0}, {1} is required in {2} rows.", [ + te.label, + field_label.bold(), + rows.length, + ]) + ); + } }); + }); + + if (lines.length) { frappe.msgprint({ message: - __("Mandatory fields required in table {0}", [te.label]) + + __("Please fill the following mandatory fields before saving:") + "

  • " + lines.join("
  • ") + - "
", + "", indicator: "red", title: __("Missing Fields"), }); frm.refresh(); - }); + } return !has_errors; From 019114642eabc9b868583954b9059e85d306b22d Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Sun, 10 May 2026 18:01:09 +0530 Subject: [PATCH 090/261] style: prettier (cherry picked from commit 34e8bd7fc7f9161454b81b3014c01dd8e63366a4) --- frappe/public/js/frappe/form/save.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index 4e96e643d842..0a6e7a222ad0 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -220,10 +220,7 @@ frappe.ui.form.check_mandatory = function (frm) { if (rows.length === te.total_rows) { lines.push( - __("In {0}, {1} is required in every row.", [ - te.label, - field_label.bold(), - ]) + __("In {0}, {1} is required in every row.", [te.label, field_label.bold()]) ); } else if (rows.length === 1) { lines.push( From d669a60b8cfb23865089324cc98eedd68500cfe4 Mon Sep 17 00:00:00 2001 From: nareshkannasln Date: Fri, 8 May 2026 13:07:53 +0530 Subject: [PATCH 091/261] fix(grid): update dynamic docfield options after configure columns (cherry picked from commit 2ba60c08be4ee9939a166bae0e9f9d3a430a8205) --- frappe/public/js/frappe/form/grid.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 2c07bc703d34..9d4b08dfb6ff 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -1156,7 +1156,9 @@ export default class Grid { if (user_settings && user_settings[this.doctype] && user_settings[this.doctype].length) { this.user_defined_columns = user_settings[this.doctype] .map((row) => { - let column = frappe.meta.get_docfield(this.doctype, row.fieldname); + let column = + this.docfields?.find((d) => d.fieldname === row.fieldname) || + frappe.meta.get_docfield(this.doctype, row.fieldname); if (column) { column.in_list_view = 1; From 0a053cd20f89abc2d17140f37c7fc29c7643b696 Mon Sep 17 00:00:00 2001 From: Kaushal Shriwas <64089478+kaulith@users.noreply.github.com> Date: Tue, 26 May 2026 21:34:18 +0530 Subject: [PATCH 092/261] fix(list-view): retain selection when cancelling Clear Assignment (cherry picked from commit c685b271b0a2b2a7b9bf64203286921f92c2598a) --- frappe/public/js/frappe/list/list_view.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 0045c31b19d1..791b2924ecdd 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -2248,21 +2248,14 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { return { label: __("Clear Assignment", null, "Button in list view actions menu"), action: () => { - frappe.confirm( - __("Are you sure you want to clear the assignments?"), - () => { - this.disable_list_update = true; - bulk_operations.clear_assignment(this.get_checked_items(true), () => { - this.disable_list_update = false; - this.clear_checked_items(); - this.refresh(); - }); - }, - () => { + frappe.confirm(__("Are you sure you want to clear the assignments?"), () => { + this.disable_list_update = true; + bulk_operations.clear_assignment(this.get_checked_items(true), () => { + this.disable_list_update = false; this.clear_checked_items(); this.refresh(); - } - ); + }); + }); }, standard: true, }; From 4af1b9e81c02ca9a0025309f1e0226dff3a25e38 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Tue, 26 May 2026 01:04:36 +0530 Subject: [PATCH 093/261] fix(child_table): hide multiple rows button on selecting rows (cherry picked from commit 422840dd28f2f2041dc5d4a3e741ce4fe6c44b5b) --- frappe/public/js/frappe/form/grid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 9d4b08dfb6ff..0cf135e3cecf 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -224,7 +224,7 @@ export default class Grid { // toggle "Add row" button this.wrapper - .find(".grid-add-row") + .find(".grid-add-row, .grid-add-multiple-rows") .toggleClass( "hidden", num_selected_rows > 0 || From 1498afdadf61c01626db1626cd9947ebf2e2e7b2 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Tue, 26 May 2026 01:07:04 +0530 Subject: [PATCH 094/261] fix(child_table): update selection banner after duplicate and delete rows (cherry picked from commit a2c52e373a71a76057f6c29113b3ba551ff67a45) --- frappe/public/js/frappe/form/grid.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 0cf135e3cecf..67bfbeb89a9d 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -243,20 +243,24 @@ export default class Grid { this.refresh_remove_rows_button(); this.refresh_duplicate_rows_button(); - this.update_selection_banner(num_selected_rows); + this.update_selection_banner(); }); } - update_selection_banner(count) { + update_selection_banner() { + const num_selected_rows = this.get_selected_children().length; + let $container = this.wrapper.find(".form-grid-container"); let $toast = this.wrapper.find("> .grid-selection-toast"); - if (count > 0) { + if (num_selected_rows > 0) { if (!$toast.length) { $toast = $( `
` ).insertAfter($container); } - $toast.find(".grid-selection-toast__message").text(__("{0} rows selected", [count])); + $toast + .find(".grid-selection-toast__message") + .text(__("{0} row(s) selected", [num_selected_rows])); $toast.show(); } else if ($toast.length) { $toast.hide(); @@ -289,6 +293,7 @@ export default class Grid { this.add_new_row(null, null, false, doc, false); this.check_range(doc.name, doc.name, false); }); + this.update_selection_banner(); } delete_rows() { @@ -543,6 +548,7 @@ export default class Grid { this.refresh_duplicate_rows_button(); this.wrapper.trigger("change"); + this.update_selection_banner(); } render_result_rows($rows) { From 6262f9b8c0e1f425c9a9e8a7fb66c11dd5e2de41 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Tue, 26 May 2026 03:12:18 +0530 Subject: [PATCH 095/261] test(child_table): add cypress tests for grid row selection behaviour Co-Authored-By: Claude Sonnet 4.6 (cherry picked from commit 3fdd193dc79b46b569baa5507b02d116c3dbed6d) # Conflicts: # cypress/integration/grid.js --- cypress/integration/grid.js | 137 ++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/cypress/integration/grid.js b/cypress/integration/grid.js index 607db6746eae..a19a0bb9af13 100644 --- a/cypress/integration/grid.js +++ b/cypress/integration/grid.js @@ -111,4 +111,141 @@ context("Grid", () => { cy.get("@table-form").find(".grid-footer-toolbar").click(); }); }); +<<<<<<< HEAD +======= + + it("shows edit button only when child table allow_bulk_edit is enabled", () => { + cy.visit("/desk/contact/Test Contact"); + cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table"); + + cy.window() + .its("cur_frm") + .then((frm) => { + const grid = frm.get_field("phone_nos").grid; + grid.meta.allow_bulk_edit = false; + grid.refresh_edit_rows_button(); + }); + + cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); + cy.get("@table").find(".grid-edit-rows").should("have.class", "hidden"); + + cy.window() + .its("cur_frm") + .then((frm) => { + const grid = frm.get_field("phone_nos").grid; + grid.meta.allow_bulk_edit = true; + grid.refresh_edit_rows_button(); + }); + + cy.get("@table").find(".grid-edit-rows").should("not.have.class", "hidden"); + }); + + it("bulk edit updates only selected child rows", () => { + const updated_phone = `99999${Date.now().toString().slice(-5)}`; + + cy.visit("/desk/contact/Test Contact"); + cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table"); + + cy.window() + .its("cur_frm") + .then((frm) => { + const grid = frm.get_field("phone_nos").grid; + grid.meta.allow_bulk_edit = true; + grid.refresh_edit_rows_button(); + + expect(frm.doc.phone_nos.length).to.be.greaterThan(1); + const phone_df = grid.docfields.find((df) => df.fieldname === "phone"); + expect(phone_df).to.exist; + cy.wrap(phone_df.label).as("phoneFieldLabel"); + cy.wrap(frm.doc.phone_nos[1].phone || "").as("secondRowPhoneBefore"); + }); + + cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); + cy.get("@table").find(".grid-edit-rows").click({ force: true }); + + cy.window() + .its("cur_dialog") + .then((dialog) => { + cy.get("@phoneFieldLabel").then((phoneFieldLabel) => { + return dialog + .set_value("field", phoneFieldLabel) + .then(() => dialog.set_value("value", updated_phone)) + .then(() => { + dialog.get_primary_btn().click(); + }); + }); + }); + + cy.window().its("cur_frm.doc.phone_nos.0.phone").should("eq", updated_phone); + cy.window() + .its("cur_frm") + .then((frm) => { + cy.get("@secondRowPhoneBefore").then((secondRowPhoneBefore) => { + expect(frm.doc.phone_nos[1].phone || "").to.equal(secondRowPhoneBefore); + }); + }); + }); + + it("hides add-row and add-multiple-rows buttons when rows are selected", () => { + cy.visit("/desk/contact/Test Contact"); + cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table"); + + cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); + + cy.get("@table").find(".grid-add-row").should("have.class", "hidden"); + cy.get("@table").find(".grid-add-multiple-rows").should("have.class", "hidden"); + + cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); + + cy.get("@table").find(".grid-add-row").should("not.have.class", "hidden"); + }); + + it("keeps selection count unchanged after duplicate_rows", () => { + cy.visit("/desk/contact/Test Contact"); + cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table"); + + cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); + cy.get("@table").find('.grid-row[data-idx="2"] .grid-row-check').click({ force: true }); + cy.get("@table").find('.grid-row[data-idx="3"] .grid-row-check').click({ force: true }); + cy.get("@table").find(".grid-selection-toast").should("be.visible"); + cy.get("@table") + .find(".grid-selection-toast__message") + .should("contain", "3 row(s) selected"); + + // duplicate_rows unchecks the original row — banner should show 1 row(s) selected. + cy.window() + .its("cur_frm") + .then((frm) => { + frm.get_field("phone_nos").grid.duplicate_rows(); + }); + + cy.get("@table") + .find(".grid-selection-toast__message") + .should("contain", "3 row(s) selected"); + }); + + it("hides selection banner after deleting selected rows", () => { + cy.visit("/desk/contact/Test Contact"); + cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table"); + + cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); + cy.get("@table").find(".grid-selection-toast").should("be.visible"); + + // Click the delete button in the UI so Cypress waits for DOM changes + cy.get("@table").find(".grid-remove-rows").click({ force: true }); + + cy.get("@table").find(".grid-selection-toast").should("not.be.visible"); + }); + + it("shows and hides selection banner on selecting and unselecting a row", () => { + cy.visit("/desk/contact/Test Contact"); + cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table"); + + cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); + cy.get("@table").find(".grid-selection-toast").should("be.visible"); + + cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); + cy.get("@table").find(".grid-selection-toast").should("not.be.visible"); + }); +>>>>>>> 3fdd193dc7 (test(child_table): add cypress tests for grid row selection behaviour) }); From ca608935a27940c258877c24a2140b7abd5cdcae Mon Sep 17 00:00:00 2001 From: Soham Kulkarni <77533095+sokumon@users.noreply.github.com> Date: Wed, 27 May 2026 00:33:07 +0530 Subject: [PATCH 096/261] chore: merge conflicts --- cypress/integration/grid.js | 75 ------------------------------------- 1 file changed, 75 deletions(-) diff --git a/cypress/integration/grid.js b/cypress/integration/grid.js index a19a0bb9af13..1e9da0c65c65 100644 --- a/cypress/integration/grid.js +++ b/cypress/integration/grid.js @@ -111,80 +111,6 @@ context("Grid", () => { cy.get("@table-form").find(".grid-footer-toolbar").click(); }); }); -<<<<<<< HEAD -======= - - it("shows edit button only when child table allow_bulk_edit is enabled", () => { - cy.visit("/desk/contact/Test Contact"); - cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table"); - - cy.window() - .its("cur_frm") - .then((frm) => { - const grid = frm.get_field("phone_nos").grid; - grid.meta.allow_bulk_edit = false; - grid.refresh_edit_rows_button(); - }); - - cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); - cy.get("@table").find(".grid-edit-rows").should("have.class", "hidden"); - - cy.window() - .its("cur_frm") - .then((frm) => { - const grid = frm.get_field("phone_nos").grid; - grid.meta.allow_bulk_edit = true; - grid.refresh_edit_rows_button(); - }); - - cy.get("@table").find(".grid-edit-rows").should("not.have.class", "hidden"); - }); - - it("bulk edit updates only selected child rows", () => { - const updated_phone = `99999${Date.now().toString().slice(-5)}`; - - cy.visit("/desk/contact/Test Contact"); - cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table"); - - cy.window() - .its("cur_frm") - .then((frm) => { - const grid = frm.get_field("phone_nos").grid; - grid.meta.allow_bulk_edit = true; - grid.refresh_edit_rows_button(); - - expect(frm.doc.phone_nos.length).to.be.greaterThan(1); - const phone_df = grid.docfields.find((df) => df.fieldname === "phone"); - expect(phone_df).to.exist; - cy.wrap(phone_df.label).as("phoneFieldLabel"); - cy.wrap(frm.doc.phone_nos[1].phone || "").as("secondRowPhoneBefore"); - }); - - cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); - cy.get("@table").find(".grid-edit-rows").click({ force: true }); - - cy.window() - .its("cur_dialog") - .then((dialog) => { - cy.get("@phoneFieldLabel").then((phoneFieldLabel) => { - return dialog - .set_value("field", phoneFieldLabel) - .then(() => dialog.set_value("value", updated_phone)) - .then(() => { - dialog.get_primary_btn().click(); - }); - }); - }); - - cy.window().its("cur_frm.doc.phone_nos.0.phone").should("eq", updated_phone); - cy.window() - .its("cur_frm") - .then((frm) => { - cy.get("@secondRowPhoneBefore").then((secondRowPhoneBefore) => { - expect(frm.doc.phone_nos[1].phone || "").to.equal(secondRowPhoneBefore); - }); - }); - }); it("hides add-row and add-multiple-rows buttons when rows are selected", () => { cy.visit("/desk/contact/Test Contact"); @@ -247,5 +173,4 @@ context("Grid", () => { cy.get("@table").find('.grid-row[data-idx="1"] .grid-row-check').click({ force: true }); cy.get("@table").find(".grid-selection-toast").should("not.be.visible"); }); ->>>>>>> 3fdd193dc7 (test(child_table): add cypress tests for grid row selection behaviour) }); From 57291c51d9887cbbd101190aa5a2035e69e95302 Mon Sep 17 00:00:00 2001 From: sokumon Date: Thu, 5 Feb 2026 14:23:05 +0530 Subject: [PATCH 097/261] fix: transform filter correctly to match route options (cherry picked from commit 231289c2332f73eb8a09eed026d7da09ea89c3d2) --- frappe/public/js/frappe/ui/sidebar/sidebar_item.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js index c25dbb10d610..8c832919e64f 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js @@ -60,6 +60,7 @@ frappe.ui.sidebar_item.TypeLink = class SidebarItem { let filters_json = JSON.parse( frappe.utils.get_filter_as_json(JSON.parse(this.item.filters)) ); + filters_json = this.transform_filters(filters_json); if (this.item.link_type == "DocType") { args.doc_view = "List"; args.route_options = filters_json; @@ -73,6 +74,15 @@ frappe.ui.sidebar_item.TypeLink = class SidebarItem { } return path; } + transform_filters(filters_json) { + for (const [key, value] of Object.entries(filters_json)) { + if (Array.isArray(value)) { + filters_json[key] = value[1]; + } + } + return filters_json; + } + prepare() {} make() { this.path = this.get_path(); From dae6e0a2cad40aaa18d8de91ef90e61e2ac3f23f Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Mon, 9 Feb 2026 12:10:55 +0530 Subject: [PATCH 098/261] fix: respect search bar display setting in desktop page (#36789) (cherry picked from commit 944302163c945c55b412843145183e3992efbfa2) --- frappe/desk/page/desktop/desktop.html | 29 ++++++++++++++------------- frappe/desk/page/desktop/desktop.py | 2 ++ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/frappe/desk/page/desktop/desktop.html b/frappe/desk/page/desktop/desktop.html index 5d3126af1766..12d7baa7f70e 100644 --- a/frappe/desk/page/desktop/desktop.html +++ b/frappe/desk/page/desktop/desktop.html @@ -8,20 +8,21 @@ alt="{{ _("App Logo") |e }}" >
- + {% if (show_search_bar) %} + + {% endif %}