From 9d094c67d779b65cc27629c6d28a673e43a0872e Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Thu, 6 Apr 2023 14:17:19 -0400 Subject: [PATCH 01/25] fix: expose DataTable globally --- frappe/public/js/frappe/views/reports/query_report.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index d6c50380f2..d877f47f21 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -2,6 +2,9 @@ // MIT License. See license.txt import DataTable from "frappe-datatable"; +// Expose DataTable globally to allow customizations. +window.DataTable = DataTable; + frappe.provide("frappe.widget.utils"); frappe.provide("frappe.views"); frappe.provide("frappe.query_reports"); @@ -933,7 +936,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (this.report_settings.get_datatable_options) { datatable_options = this.report_settings.get_datatable_options(datatable_options); } - this.datatable = new DataTable(this.$report[0], datatable_options); + this.datatable = new window.DataTable(this.$report[0], datatable_options); } if (typeof this.report_settings.initial_depth == "number") { From 361e44de1db9aac2844b0389cd244bd3506c72af Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 11:50:26 +0530 Subject: [PATCH 02/25] fix(translations)!: load translation in installed order - translations are loaded in apps.txt order this doesnt make much sense. - translations are loaded from apps which aren't even installed, again doesn't make sense. Breaking but necessary change. --- .../core/doctype/translation/test_translation.py | 4 ++-- frappe/tests/test_translate.py | 5 ----- frappe/translate.py | 14 +++++++------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/frappe/core/doctype/translation/test_translation.py b/frappe/core/doctype/translation/test_translation.py index 5602fa2c2d..13ce711c66 100644 --- a/frappe/core/doctype/translation/test_translation.py +++ b/frappe/core/doctype/translation/test_translation.py @@ -3,7 +3,7 @@ import frappe from frappe import _ from frappe.tests.utils import FrappeTestCase -from frappe.translate import clear_cache +from frappe.translate import APP_TRANSLATION_KEY, clear_cache class TestTranslation(FrappeTestCase): @@ -115,4 +115,4 @@ def create_translation(key, val): def clear_translation_cache(): - frappe.cache().delete_key("translations_from_apps", shared=True) + frappe.cache().delete_key(APP_TRANSLATION_KEY) diff --git a/frappe/tests/test_translate.py b/frappe/tests/test_translate.py index a5e2876241..c605810837 100644 --- a/frappe/tests/test_translate.py +++ b/frappe/tests/test_translate.py @@ -8,7 +8,6 @@ from unittest.mock import patch import frappe import frappe.translate from frappe import _ -from frappe.core.doctype.translation.test_translation import clear_translation_cache from frappe.tests.utils import FrappeTestCase from frappe.translate import ( extract_javascript, @@ -39,15 +38,11 @@ class TestTranslate(FrappeTestCase): if self._testMethodName in self.guest_sessions_required: frappe.set_user("Guest") - clear_translation_cache() - def tearDown(self): frappe.form_dict.pop("_lang", None) if self._testMethodName in self.guest_sessions_required: frappe.set_user("Administrator") - clear_translation_cache() - def test_extract_message_from_file(self): data = frappe.translate.get_messages_from_file(translation_string_file) exp_filename = "apps/frappe/frappe/tests/translation_test_file.txt" diff --git a/frappe/translate.py b/frappe/translate.py index 5179daa545..03bd49e4c6 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -171,7 +171,7 @@ def get_dict(fortype: str, name: str | None = None) -> dict[str, str]: fortype = fortype.lower() cache = frappe.cache() asset_key = fortype + ":" + (name or "-") - translation_assets = cache.hget("translation_assets", frappe.local.lang, shared=True) or {} + translation_assets = cache.hget("translation_assets", frappe.local.lang) or {} if asset_key not in translation_assets: messages = [] @@ -211,7 +211,7 @@ def get_dict(fortype: str, name: str | None = None) -> dict[str, str]: # remove untranslated message_dict = {k: v for k, v in message_dict.items() if k != v} translation_assets[asset_key] = message_dict - cache.hset("translation_assets", frappe.local.lang, translation_assets, shared=True) + cache.hset("translation_assets", frappe.local.lang, translation_assets) translation_map: dict = translation_assets[asset_key] @@ -310,7 +310,7 @@ def get_translations_from_apps(lang, apps=None): def _get_from_disk(): translations = {} - for app in apps or frappe.get_all_apps(True): + for app in apps or frappe.get_installed_apps(_ensure_on_bench=True): path = os.path.join(frappe.get_pymodule_path(app), "translations", lang + ".csv") translations.update(get_translation_dict_from_file(path, lang, app) or {}) if "-" in lang: @@ -321,7 +321,7 @@ def get_translations_from_apps(lang, apps=None): return translations - return frappe.cache().hget(APP_TRANSLATION_KEY, lang, shared=True, generator=_get_from_disk) + return frappe.cache().hget(APP_TRANSLATION_KEY, lang, generator=_get_from_disk) def get_translation_dict_from_file(path, lang, app, throw=False) -> dict[str, str]: @@ -375,8 +375,8 @@ def clear_cache(): # clear translations saved in boot cache cache.delete_key("bootinfo") - cache.delete_key("translation_assets", shared=True) - cache.delete_key(APP_TRANSLATION_KEY, shared=True) + cache.delete_key("translation_assets") + cache.delete_key(APP_TRANSLATION_KEY) cache.delete_key(USER_TRANSLATION_KEY) cache.delete_key(MERGED_TRANSLATION_KEY) @@ -687,7 +687,7 @@ def get_messages_from_include_files(app_name=None): def get_all_messages_from_js_files(app_name=None): """Extracts all translatable strings from app `.js` files""" messages = [] - for app in [app_name] if app_name else frappe.get_installed_apps(): + for app in [app_name] if app_name else frappe.get_installed_apps(_ensure_on_bench=True): if os.path.exists(frappe.get_app_path(app, "public")): for basepath, folders, files in os.walk(frappe.get_app_path(app, "public")): if "frappe/public/js/lib" in basepath: From 0b8b8294836dfc949ec552e89d571b64d909dbea Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 11:59:19 +0530 Subject: [PATCH 03/25] perf: dont cache intermediate translation files Just caching final files for each language is enough, duplicating doesn't help much. --- .../doctype/translation/test_translation.py | 10 +------- frappe/translate.py | 25 ++++++++----------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/frappe/core/doctype/translation/test_translation.py b/frappe/core/doctype/translation/test_translation.py index 13ce711c66..a64715a32e 100644 --- a/frappe/core/doctype/translation/test_translation.py +++ b/frappe/core/doctype/translation/test_translation.py @@ -3,7 +3,7 @@ import frappe from frappe import _ from frappe.tests.utils import FrappeTestCase -from frappe.translate import APP_TRANSLATION_KEY, clear_cache +from frappe.translate import clear_cache class TestTranslation(FrappeTestCase): @@ -37,20 +37,16 @@ class TestTranslation(FrappeTestCase): frappe.local.lang = "es" - clear_translation_cache() self.assertTrue(_(data[0][0]), data[0][1]) - clear_translation_cache() self.assertTrue(_(data[1][0]), data[1][1]) frappe.local.lang = "es-MX" # different translation for es-MX - clear_translation_cache() self.assertTrue(_(data[2][0]), data[2][1]) # from spanish (general) - clear_translation_cache() self.assertTrue(_(data[1][0]), data[1][1]) def test_multi_language_translations(self): @@ -112,7 +108,3 @@ def create_translation(key, val): translation.translated_text = val[1] translation.save() return translation - - -def clear_translation_cache(): - frappe.cache().delete_key(APP_TRANSLATION_KEY) diff --git a/frappe/translate.py b/frappe/translate.py index 03bd49e4c6..041e983432 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -56,7 +56,6 @@ CSV_STRIP_WHITESPACE_PATTERN = re.compile(r"{\s?([0-9]+)\s?}") # Cache keys MERGED_TRANSLATION_KEY = "merged_translations" -APP_TRANSLATION_KEY = "translations_from_apps" USER_TRANSLATION_KEY = "lang_user_translations" @@ -308,20 +307,17 @@ def get_translations_from_apps(lang, apps=None): if lang == "en": return {} - def _get_from_disk(): - translations = {} - for app in apps or frappe.get_installed_apps(_ensure_on_bench=True): - path = os.path.join(frappe.get_pymodule_path(app), "translations", lang + ".csv") - translations.update(get_translation_dict_from_file(path, lang, app) or {}) - if "-" in lang: - parent = lang.split("-", 1)[0] - parent_translations = get_translations_from_apps(parent) - parent_translations.update(translations) - return parent_translations + translations = {} + for app in apps or frappe.get_installed_apps(_ensure_on_bench=True): + path = os.path.join(frappe.get_pymodule_path(app), "translations", lang + ".csv") + translations.update(get_translation_dict_from_file(path, lang, app) or {}) + if "-" in lang: + parent = lang.split("-", 1)[0] + parent_translations = get_translations_from_apps(parent) + parent_translations.update(translations) + return parent_translations - return translations - - return frappe.cache().hget(APP_TRANSLATION_KEY, lang, generator=_get_from_disk) + return translations def get_translation_dict_from_file(path, lang, app, throw=False) -> dict[str, str]: @@ -376,7 +372,6 @@ def clear_cache(): # clear translations saved in boot cache cache.delete_key("bootinfo") cache.delete_key("translation_assets") - cache.delete_key(APP_TRANSLATION_KEY) cache.delete_key(USER_TRANSLATION_KEY) cache.delete_key(MERGED_TRANSLATION_KEY) From 82ec29702e61ac6f36398f2045b94cd9d78a526f Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Tue, 11 Apr 2023 16:24:47 +0530 Subject: [PATCH 04/25] fix: consider user email if send me a copy is checked (#20627) * fix: consider user email if send me a copy is checked * test(communication): test cc with include sender --- frappe/core/doctype/communication/mixins.py | 13 ++++++--- .../communication/test_communication.py | 29 ++++++++++++++----- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/frappe/core/doctype/communication/mixins.py b/frappe/core/doctype/communication/mixins.py index 24b6a8fafb..73e94fad09 100644 --- a/frappe/core/doctype/communication/mixins.py +++ b/frappe/core/doctype/communication/mixins.py @@ -66,11 +66,16 @@ class CommunicationEmailMixin: cc = self.cc_list() - # Need to inform parent document owner incase communication is created through inbound mail if include_sender: - cc.append(self.sender_mailid) + sender = self.sender_mailid + # if user has selected send_me_a_copy, use their email as sender + if frappe.session.user not in frappe.STANDARD_USERS: + sender = frappe.db.get_value("User", frappe.session.user, "email") + cc.append(sender) + if is_inbound_mail_communcation: - if (doc_owner := self.get_owner()) and (doc_owner not in frappe.STANDARD_USERS): + # inform parent document owner incase communication is created through inbound mail + if doc_owner := self.get_owner(): cc.append(doc_owner) cc = set(cc) - {self.sender_mailid} cc.update(self.get_assignees()) @@ -82,7 +87,7 @@ class CommunicationEmailMixin: if is_inbound_mail_communcation: cc = cc - set(self.cc_list() + self.to_list()) - self._final_cc = [m for m in cc if m not in frappe.STANDARD_USERS] + self._final_cc = [m for m in cc if m and m not in frappe.STANDARD_USERS] return self._final_cc def get_mail_cc_with_displayname(self, is_inbound_mail_communcation=False, include_sender=False): diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py index 04e57f10cf..7f2d36d60a 100644 --- a/frappe/core/doctype/communication/test_communication.py +++ b/frappe/core/doctype/communication/test_communication.py @@ -308,6 +308,7 @@ class TestCommunicationEmailMixin(FrappeTestCase): "recipients": recipients, "cc": cc, "bcc": bcc, + "sender": "sender@test.com", } ).insert(ignore_permissions=True) @@ -327,14 +328,26 @@ class TestCommunicationEmailMixin(FrappeTestCase): comm.delete() def test_cc(self): - to_list = ["to@test.com"] - cc_list = ["cc+1@test.com", "cc ", "to@test.com"] - user = self.new_user(email="cc+1@test.com", thread_notify=0) - comm = self.new_communication(recipients=to_list, cc=cc_list) - res = comm.get_mail_cc_with_displayname() - self.assertCountEqual(res, ["cc "]) - user.delete() - comm.delete() + def test(assertion, cc_list=None, set_user_as=None, include_sender=False, thread_notify=False): + if set_user_as: + frappe.set_user(set_user_as) + + user = self.new_user(email="cc+1@test.com", thread_notify=thread_notify) + comm = self.new_communication(recipients=["to@test.com"], cc=cc_list) + res = comm.get_mail_cc_with_displayname(include_sender=include_sender) + + frappe.set_user("Administrator") + user.delete() + comm.delete() + + self.assertEqual(res, assertion) + + # test filter_thread_notification_disbled_users and filter_mail_recipients + test(["cc "], cc_list=["cc+1@test.com", "cc ", "to@test.com"]) + + # test include_sender + test(["sender@test.com"], include_sender=True, thread_notify=True) + test(["cc+1@test.com"], include_sender=True, thread_notify=True, set_user_as="cc+1@test.com") def test_bcc(self): bcc_list = [ From d5a9f198ec62821a383d4a70869e00cb80bc1d79 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 18:08:19 +0530 Subject: [PATCH 05/25] refactor: set docfield options without method (#20653) --- frappe/workflow/doctype/workflow/workflow.js | 24 ++++++++++++-------- frappe/workflow/doctype/workflow/workflow.py | 8 ------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index dd9fa4798b..6abdf9eb09 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -40,17 +40,21 @@ frappe.ui.form.on("Workflow", { }, update_field_options: function (frm) { var doc = frm.doc; - if (doc.document_type) { - const get_field_method = - "frappe.workflow.doctype.workflow.workflow.get_fieldnames_for"; - frappe.xcall(get_field_method, { doctype: doc.document_type }).then((resp) => { - frm.fields_dict.states.grid.update_docfield_property( - "update_field", - "options", - [""].concat(resp) - ); - }); + if (!doc.document_type) { + return; } + frappe.model.with_doctype(doc.document_type, () => { + const fieldnames = frappe + .get_meta(doc.document_type) + .fields.filter((field) => !frappe.model.no_value_type.includes(field.fieldtype)) + .map((field) => field.fieldname); + + frm.fields_dict.states.grid.update_docfield_property( + "update_field", + "options", + [""].concat(fieldnames) + ); + }); }, create_warning_dialog: function (frm) { const warning_html = `

diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index 1b26d0c771..54c8c6f61b 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -124,14 +124,6 @@ class Workflow(Document): ) -@frappe.whitelist() -def get_fieldnames_for(doctype): - frappe.has_permission(doctype=doctype, ptype='read', throw=True) - return [ - f.fieldname for f in frappe.get_meta(doctype).fields if f.fieldname not in no_value_fields - ] - - @frappe.whitelist() def get_workflow_state_count(doctype, workflow_state_field, states): frappe.has_permission(doctype=doctype, ptype='read', throw=True) From 9f1feaab025ea386d94e7ebbed51779cf2d1eb79 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 11 Apr 2023 15:44:03 +0200 Subject: [PATCH 06/25] refactor: pretty_date --- frappe/utils/data.py | 43 ++++--------------------------------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index d3ffb8b749..f2bcc8e4df 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1518,51 +1518,16 @@ def pretty_date(iso_datetime: datetime.datetime | str) -> str: long ago the date represents. Ported from PrettyDate by John Resig """ - from frappe import _ - if not iso_datetime: return "" - import math + + from babel.dates import format_timedelta if isinstance(iso_datetime, str): iso_datetime = datetime.datetime.strptime(iso_datetime, DATETIME_FORMAT) now_dt = datetime.datetime.strptime(now(), DATETIME_FORMAT) - dt_diff = now_dt - iso_datetime - - # available only in python 2.7+ - # dt_diff_seconds = dt_diff.total_seconds() - - dt_diff_seconds = dt_diff.days * 86400.0 + dt_diff.seconds - - dt_diff_days = math.floor(dt_diff_seconds / 86400.0) - - # differnt cases - if dt_diff_seconds < 60.0: - return _("just now") - elif dt_diff_seconds < 120.0: - return _("1 minute ago") - elif dt_diff_seconds < 3600.0: - return _("{0} minutes ago").format(cint(math.floor(dt_diff_seconds / 60.0))) - elif dt_diff_seconds < 7200.0: - return _("1 hour ago") - elif dt_diff_seconds < 86400.0: - return _("{0} hours ago").format(cint(math.floor(dt_diff_seconds / 3600.0))) - elif dt_diff_days == 1.0: - return _("Yesterday") - elif dt_diff_days < 7.0: - return _("{0} days ago").format(cint(dt_diff_days)) - elif dt_diff_days < 14: - return _("1 week ago") - elif dt_diff_days < 31.0: - return _("{0} weeks ago").format(dt_diff_days // 7) - elif dt_diff_days < 61.0: - return _("1 month ago") - elif dt_diff_days < 365.0: - return _("{0} months ago").format(dt_diff_days // 30) - elif dt_diff_days < 730.0: - return _("1 year ago") - else: - return _("{0} years ago").format(dt_diff_days // 365) + locale = frappe.local.lang.replace("-", "_") if frappe.local.lang else None + return format_timedelta(iso_datetime - now_dt, add_direction=True, locale=locale) def comma_or(some_list, add_quotes=True): From fcb705b41da51230e280df00c1aad08ed1f83f5a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 11 Apr 2023 15:50:17 +0200 Subject: [PATCH 07/25] chore: docstring for pretty_date --- frappe/utils/data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index f2bcc8e4df..a51cdee04a 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1514,9 +1514,9 @@ def escape_html(text: str) -> str: def pretty_date(iso_datetime: datetime.datetime | str) -> str: """ - Takes an ISO time and returns a string representing how - long ago the date represents. - Ported from PrettyDate by John Resig + Return a localized string representation of the delta to the current system time. + + For example, "1 hour ago", "2 days ago", "in 5 seconds", etc. """ if not iso_datetime: return "" From 44bb745035d6e558d8878b4a582ef78fd4346a3a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 11 Apr 2023 15:56:05 +0200 Subject: [PATCH 08/25] fix: change pretty date expectations in test --- frappe/tests/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py index dce2a159ac..4b362e7b47 100644 --- a/frappe/tests/test_utils.py +++ b/frappe/tests/test_utils.py @@ -611,12 +611,12 @@ class TestDateUtils(FrappeTestCase): now = get_datetime() test_cases = { - now: _("just now"), + now: _("1 second ago"), add_to_date(now, minutes=-1): _("1 minute ago"), add_to_date(now, minutes=-3): _("3 minutes ago"), add_to_date(now, hours=-1): _("1 hour ago"), add_to_date(now, hours=-2): _("2 hours ago"), - add_to_date(now, days=-1): _("Yesterday"), + add_to_date(now, days=-1): _("1 day ago"), add_to_date(now, days=-5): _("5 days ago"), add_to_date(now, days=-8): _("1 week ago"), add_to_date(now, days=-14): _("2 weeks ago"), From a1396349fe798d4414fb95cc00f6437918289815 Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Wed, 12 Apr 2023 00:00:53 +0530 Subject: [PATCH 09/25] chore: translate successful redirection message in web_form --- frappe/website/doctype/web_form/templates/web_form.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frappe/website/doctype/web_form/templates/web_form.html b/frappe/website/doctype/web_form/templates/web_form.html index f02acd5b07..9caf1983b3 100644 --- a/frappe/website/doctype/web_form/templates/web_form.html +++ b/frappe/website/doctype/web_form/templates/web_form.html @@ -137,11 +137,8 @@ {% if success_url %}

- Click on this - {{_("URL")}} - if you are not redirected within - 5 - seconds. + {% set success_link = "link".format(success_url) %} + {{ _("Click on this {0} if you are not redirected within 5 seconds").format(success_link) }}

{% else %} From 66df0e9abcc62e1a538af20a9ff5ef16dc9f3109 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 12 Apr 2023 10:53:34 +0530 Subject: [PATCH 10/25] fix: update property if field exist --- frappe/public/js/frappe/form/grid.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index ee3b4508bf..69378d3c30 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -1189,7 +1189,10 @@ export default class Grid { this.docfields.find((d) => d.fieldname === fieldname)[property] = value; if (this.user_defined_columns && this.user_defined_columns.length > 0) { - this.user_defined_columns.find((d) => d.fieldname === fieldname)[property] = value; + let field = this.user_defined_columns.find((d) => d.fieldname === fieldname); + if (field && Object.keys(field).includes(property)) { + field[property] = value; + } } this.debounced_refresh(); From c5f36a49799658655fd4c0ec64692120e2a2817b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 12 Apr 2023 10:56:05 +0530 Subject: [PATCH 11/25] fix(UX): workspace breadcrumbs based on history (#20529) * fix(UX): Resolve breadcrumb conflicts from history Same report can be part of 2 workspace, in which case use breadcrumbs from last workspace. * fix: make sure last workspace belongs to same module at least --- frappe/boot.py | 2 +- frappe/desk/doctype/workspace/workspace.py | 19 +++++-- frappe/public/js/frappe/views/breadcrumbs.js | 54 ++++++++++++++------ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 0cab7a060c..83c9902020 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -130,7 +130,7 @@ def load_desktop_data(bootinfo): from frappe.desk.desktop import get_workspace_sidebar_items bootinfo.allowed_workspaces = get_workspace_sidebar_items().get("pages") - bootinfo.module_page_map = get_controller("Workspace").get_module_page_map() + bootinfo.module_wise_workspaces = get_controller("Workspace").get_module_wise_workspaces() bootinfo.dashboards = frappe.get_all("Dashboard") diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index f7d9e8ac3e..0866795538 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -1,6 +1,7 @@ # Copyright (c) 2020, Frappe Technologies and contributors # License: MIT. See LICENSE +from collections import defaultdict from json import loads import frappe @@ -49,12 +50,22 @@ class Workspace(Document): delete_folder(self.module, "Workspace", self.title) @staticmethod - def get_module_page_map(): - pages = frappe.get_all( - "Workspace", fields=["name", "module"], filters={"for_user": ""}, as_list=1 + def get_module_wise_workspaces(): + workspaces = frappe.get_all( + "Workspace", + fields=["name", "module"], + filters={"for_user": "", "public": 1}, + order_by="creation", ) - return {page[1]: page[0] for page in pages if page[1]} + module_workspaces = defaultdict(list) + + for workspace in workspaces: + if not workspace.module: + continue + module_workspaces[workspace.module].append(workspace.name) + + return module_workspaces def get_link_groups(self): cards = [] diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js index 74560cebbc..9b1107de1c 100644 --- a/frappe/public/js/frappe/views/breadcrumbs.js +++ b/frappe/public/js/frappe/views/breadcrumbs.js @@ -82,25 +82,33 @@ frappe.breadcrumbs = { this.$breadcrumbs.append(html); }, + get last_route() { + return frappe.route_history.slice(-2)[0]; + }, + set_workspace_breadcrumb(breadcrumbs) { - // get preferred module for breadcrumbs, based on sent via module + // get preferred module for breadcrumbs, based on history and module if (!breadcrumbs.workspace) { this.set_workspace(breadcrumbs); } - - if (breadcrumbs.workspace) { - if ( - !breadcrumbs.module_info.blocked && - frappe.visible_modules.includes(breadcrumbs.module_info.module) - ) { - $( - `
  • ${__( - breadcrumbs.workspace - )}
  • ` - ).appendTo(this.$breadcrumbs); - } + if (!breadcrumbs.workspace) { + return; } + + if ( + breadcrumbs.module_info && + (breadcrumbs.module_info.blocked || + !frappe.visible_modules.includes(breadcrumbs.module_info.module)) + ) { + return; + } + + $( + `
  • ${__( + breadcrumbs.workspace + )}
  • ` + ).appendTo(this.$breadcrumbs); }, set_workspace(breadcrumbs) { @@ -117,6 +125,19 @@ frappe.breadcrumbs = { breadcrumbs.module = this.preferred[breadcrumbs.doctype]; } + // guess from last route + if (this.last_route?.[0] == "Workspaces") { + let last_workspace = this.last_route[1]; + + if ( + breadcrumbs.module && + frappe.boot.module_wise_workspaces[breadcrumbs.module]?.includes(last_workspace) + ) { + breadcrumbs.workspace = last_workspace; + return; + } + } + if (breadcrumbs.module) { if (this.module_map[breadcrumbs.module]) { breadcrumbs.module = this.module_map[breadcrumbs.module]; @@ -125,8 +146,11 @@ frappe.breadcrumbs = { breadcrumbs.module_info = frappe.get_module(breadcrumbs.module); // set workspace - if (breadcrumbs.module_info && frappe.boot.module_page_map[breadcrumbs.module]) { - breadcrumbs.workspace = frappe.boot.module_page_map[breadcrumbs.module]; + if ( + breadcrumbs.module_info && + frappe.boot.module_wise_workspaces[breadcrumbs.module] + ) { + breadcrumbs.workspace = frappe.boot.module_wise_workspaces[breadcrumbs.module][0]; } } }, From 3e95c00fd0c01dd1e970c79297fa46432d576e16 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 12 Apr 2023 11:51:24 +0530 Subject: [PATCH 12/25] fix: dont track webhook request log (#20663) logs dont change [skip ci] --- .../doctype/webhook_request_log/webhook_request_log.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json b/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json index ed5201df1f..676df48f3b 100644 --- a/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json +++ b/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json @@ -79,7 +79,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-02-24 14:59:24.743552", + "modified": "2023-04-12 11:50:01.702862", "modified_by": "Administrator", "module": "Integrations", "name": "Webhook Request Log", @@ -101,6 +101,5 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [], - "track_changes": 1 + "states": [] } \ No newline at end of file From c7632d0e51b4092e0138fbb48d1ef3d26112c616 Mon Sep 17 00:00:00 2001 From: phot0n Date: Wed, 12 Apr 2023 13:20:32 +0530 Subject: [PATCH 13/25] fix: use frappe.log_error in EmailServer exception handling --- frappe/email/receive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 59d41b543f..27b8867f84 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -151,7 +151,7 @@ class EmailServer: except _socket.error: # log performs rollback and logs error in Error Log - self.log_error("POP: Unable to connect") + frappe.log_error("POP: Unable to connect") # Invalid mail server -- due to refusing connection frappe.msgprint(_("Invalid Mail Server. Please rectify and try again.")) @@ -332,7 +332,7 @@ class EmailServer: else: # log performs rollback and logs error in Error Log - self.log_error("Unable to fetch email", self.make_error_msg(msg_num, incoming_mail)) + frappe.log_error("Unable to fetch email", self.make_error_msg(msg_num, incoming_mail)) self.errors = True frappe.db.rollback() From f46d1aefa9fce9f5ef04e6712a85a41f9bdfbb35 Mon Sep 17 00:00:00 2001 From: Mohammad Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> Date: Wed, 12 Apr 2023 13:38:23 +0530 Subject: [PATCH 14/25] refactor: use urljoin to build picture url (#20664) --- frappe/oauth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/oauth.py b/frappe/oauth.py index 8099bdab45..8955078342 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -3,7 +3,7 @@ import datetime import hashlib import re from http import cookies -from urllib.parse import unquote, urlparse +from urllib.parse import unquote, urlparse, urljoin import jwt import pytz @@ -575,7 +575,7 @@ def get_userinfo(user): if frappe.utils.validate_url(user.user_image, valid_schemes=valid_url_schemes): picture = user.user_image else: - picture = frappe_server_url + "/" + user.user_image + picture = urljoin(frappe_server_url, user.user_image) userinfo = frappe._dict( { From 398fe0d84f6bc8407d088b8964fdc757b0228f9d Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 12 Apr 2023 16:34:20 +0530 Subject: [PATCH 15/25] fix: group buttons are overlapping --- frappe/public/scss/common/css_variables.scss | 2 ++ frappe/public/scss/desk/dark.scss | 2 ++ frappe/public/scss/desk/global.scss | 15 +++++++++++ frappe/public/scss/desk/list.scss | 27 ++++---------------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/frappe/public/scss/common/css_variables.scss b/frappe/public/scss/common/css_variables.scss index f22b587405..ae53e99518 100644 --- a/frappe/public/scss/common/css_variables.scss +++ b/frappe/public/scss/common/css_variables.scss @@ -243,6 +243,8 @@ $input-height: 28px !default; --highlight-color: var(--gray-50); --yellow-highlight-color: var(--yellow-50); + --btn-group-border-color: var(--gray-300); + --field-placeholder-color: var(--gray-50); --highlight-shadow: 1px 1px 10px var(--blue-50), 0px 0px 4px var(--blue-600); diff --git a/frappe/public/scss/desk/dark.scss b/frappe/public/scss/desk/dark.scss index e1f210c440..ced95d7f69 100644 --- a/frappe/public/scss/desk/dark.scss +++ b/frappe/public/scss/desk/dark.scss @@ -100,6 +100,8 @@ --highlight-color: var(--gray-700); --yellow-highlight-color: var(--yellow-700); + --btn-group-border-color: var(--gray-800); + --field-placeholder-color: var(--gray-700); --highlight-shadow: 1px 1px 10px var(--blue-900), 0px 0px 4px var(--blue-500); diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index cb66de9920..1bf5e37b3e 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -234,6 +234,21 @@ h2 { font-size: var(--text-md); } +.btn-group { + .btn { + box-shadow: none; + outline: 1px solid var(--btn-group-border-color); + + &:not(:first-child) { + margin-left: 1px; + } + + &:focus { + outline: 2px solid var(--dark-border-color); + } + } +} + .btn-xs { @extend .btn-sm; line-height: 1.2; diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss index 31d1661abb..30bf1d6499 100644 --- a/frappe/public/scss/desk/list.scss +++ b/frappe/public/scss/desk/list.scss @@ -189,29 +189,12 @@ $level-margin-right: 8px; .list-paging-area, .footnote-area { border-top: 1px solid var(--border-color); - .btn-group { - box-shadow: var(--drop-shadow); - border-radius: var(--border-radius-md); - - &> .btn:nth-child(2) { - border-left: none; - border-right: none; - } - - .btn-paging { - box-shadow: none; - margin-left: 0px !important; - border: 1px solid var(--dark-border-color); - - &.btn-info { - background-color: var(--gray-600); - border-color: var(--gray-600); - color: var(--white); - font-weight: var(--text-bold); - } - } + .btn-group .btn-paging.btn-info { + background-color: var(--gray-600); + border-color: var(--gray-600); + color: var(--white); + font-weight: var(--text-bold); } - } .frappe-card { From 0cab0b830da637984f46bfa4af246d3c9098eca0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 13 Apr 2023 12:19:08 +0530 Subject: [PATCH 16/25] refactor: replace imghdr with filetype (#20680) * refactor: replace `imaghdr` with `filetype` ``` 11:52:06 worker.1 | /home/ankush/benches/develop/apps/frappe/frappe/core/doctype/file/utils.py:2: DeprecationWarning: 'imghdr' is deprecated and slated for removal in Python 3.13 ``` * feat: improved extension guessing using filecontent --- frappe/core/doctype/file/test_file.py | 8 ++++++++ frappe/core/doctype/file/utils.py | 10 ++++++---- frappe/oauth.py | 2 +- frappe/workflow/doctype/workflow/workflow.py | 2 +- pyproject.toml | 1 + 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 86bd69eb5f..57c9d9a11f 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -17,6 +17,7 @@ from frappe.core.api.file import ( move_file, unzip_file, ) +from frappe.core.doctype.file.utils import get_extension from frappe.exceptions import ValidationError from frappe.tests.utils import FrappeTestCase from frappe.utils import get_files_path @@ -739,3 +740,10 @@ class TestFileOptimization(FrappeTestCase): size_after_rollback = os.stat(image_path).st_size self.assertEqual(size_before_optimization, size_after_rollback) + + def test_image_header_guessing(self): + file_path = frappe.get_app_path("frappe", "tests/data/sample_image_for_optimization.jpg") + with open(file_path, "rb") as f: + file_content = f.read() + + self.assertEqual(get_extension("", None, file_content), "jpeg") diff --git a/frappe/core/doctype/file/utils.py b/frappe/core/doctype/file/utils.py index 17a092e340..1d0d145303 100644 --- a/frappe/core/doctype/file/utils.py +++ b/frappe/core/doctype/file/utils.py @@ -1,5 +1,4 @@ import hashlib -import imghdr import mimetypes import os import re @@ -7,6 +6,7 @@ from io import BytesIO from typing import TYPE_CHECKING, Optional from urllib.parse import unquote +import filetype import requests import requests.exceptions from PIL import Image @@ -76,9 +76,11 @@ def get_extension( mimetype = mimetypes.guess_type(filename + "." + extn)[0] - if mimetype is None or not mimetype.startswith("image/") and content: - # detect file extension by reading image header properties - extn = imghdr.what(filename + "." + (extn or ""), h=content) + if mimetype is None and extn is None and content: + # detect file extension by using filetype matchers + _type_info = filetype.match(content) + if _type_info: + extn = _type_info.extension return extn diff --git a/frappe/oauth.py b/frappe/oauth.py index 8955078342..2d25b5dfb5 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -3,7 +3,7 @@ import datetime import hashlib import re from http import cookies -from urllib.parse import unquote, urlparse, urljoin +from urllib.parse import unquote, urljoin, urlparse import jwt import pytz diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index 54c8c6f61b..018b567ee9 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -126,7 +126,7 @@ class Workflow(Document): @frappe.whitelist() def get_workflow_state_count(doctype, workflow_state_field, states): - frappe.has_permission(doctype=doctype, ptype='read', throw=True) + frappe.has_permission(doctype=doctype, ptype="read", throw=True) states = frappe.parse_json(states) result = frappe.get_all( doctype, diff --git a/pyproject.toml b/pyproject.toml index daa0748e5f..5429682a33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "Babel~=2.12.1", "Click~=8.1.3", "filelock~=3.8.0", + "filetype~=1.2.0", "GitPython~=3.1.30", "Jinja2~=3.1.2", "Pillow~=9.3.0", From ac351166b24782066c5ee22bb09c3a00d2083fb7 Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Thu, 13 Apr 2023 12:36:14 +0530 Subject: [PATCH 17/25] fix: pull from email account every 10 mins instead of 4 mins (by default) (#20675) --- frappe/hooks.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index effc84a873..b055f9dc8e 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -186,11 +186,13 @@ scheduler_events = { "frappe.oauth.delete_oauth2_data", "frappe.website.doctype.web_page.web_page.check_publish_status", "frappe.twofactor.delete_all_barcodes_for_users", - ] + ], + "0/10 * * * *": [ + "frappe.email.doctype.email_account.email_account.pull", + ], }, "all": [ "frappe.email.queue.flush", - "frappe.email.doctype.email_account.email_account.pull", "frappe.email.doctype.email_account.email_account.notify_unreplied", "frappe.utils.global_search.sync_global_search", "frappe.monitor.flush", From cbb7c4a91cdf84c88629deda1d740ba3c7df2eea Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 13 Apr 2023 12:46:27 +0530 Subject: [PATCH 18/25] test: fix imghdr test --- frappe/core/doctype/file/test_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 57c9d9a11f..51e065f710 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -462,7 +462,7 @@ class TestFile(FrappeTestCase): ).insert(ignore_permissions=True) test_file.make_thumbnail() - self.assertTrue(test_file.thumbnail_url.endswith("_small.jpeg")) + self.assertTrue(test_file.thumbnail_url.endswith("_small.jpg")) # test local image test_file.db_set("thumbnail_url", None) @@ -746,4 +746,4 @@ class TestFileOptimization(FrappeTestCase): with open(file_path, "rb") as f: file_content = f.read() - self.assertEqual(get_extension("", None, file_content), "jpeg") + self.assertEqual(get_extension("", None, file_content), "jpg") From 6b2b1618760dc39e4cb50ecfa48a4c25ef39564f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 13 Apr 2023 14:11:52 +0530 Subject: [PATCH 19/25] fix: filter with capitalized operator was getting bypassed --- frappe/desk/reportview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 00ae27d145..b450b734e9 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -678,7 +678,7 @@ def get_filters_cond( for f in filters: if isinstance(f[1], str) and f[1][0] == "!": flt.append([doctype, f[0], "!=", f[1][1:]]) - elif isinstance(f[1], (list, tuple)) and f[1][0] in ( + elif isinstance(f[1], (list, tuple)) and f[1][0].lower() in ( ">", "<", ">=", From b62bb8b0ecfeee5ffd2a1bba3a936c99d1e42ca3 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Thu, 13 Apr 2023 17:42:21 +0530 Subject: [PATCH 20/25] fix: allow filter values to be saved in custom report (#20623) --- frappe/core/doctype/report/report.json | 6 ++++- frappe/core/doctype/report/report.py | 2 +- frappe/core/doctype/report/test_report.py | 5 ++-- frappe/desk/query_report.py | 18 ++++++++++--- .../js/frappe/views/reports/query_report.js | 26 +++++++++++++++++-- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index 37dce73dda..9b6b04afcc 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -148,11 +148,13 @@ { "collapsible": 1, "collapsible_depends_on": "filters", + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "filters_section", "fieldtype": "Section Break", "label": "Filters" }, { + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "filters", "fieldtype": "Table", "label": "Filters", @@ -161,11 +163,13 @@ { "collapsible": 1, "collapsible_depends_on": "columns", + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "columns_section", "fieldtype": "Section Break", "label": "Columns" }, { + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "columns", "fieldtype": "Table", "label": "Columns", @@ -182,7 +186,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2022-11-20 14:56:36.578412", + "modified": "2023-04-07 18:18:11.782178", "modified_by": "Administrator", "module": "Core", "name": "Report", diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index ef38387e57..ca1e7724c1 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -169,7 +169,7 @@ class Report(Document): return columns, result - def run_query_report(self, filters, user, ignore_prepared_report=False): + def run_query_report(self, filters=None, user=None, ignore_prepared_report=False): columns, result = [], [] data = frappe.desk.query_report.run( self.name, filters=filters, user=user, ignore_prepared_report=ignore_prepared_report diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 0e1ed80eda..670b6b7410 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -118,11 +118,10 @@ class TestReport(FrappeTestCase): } ] ), + json.dumps({"user": "Administrator", "doctype": "User"}), ) custom_report = frappe.get_doc("Report", custom_report_name) - columns, result = custom_report.run_query_report( - filters={"user": "Administrator", "doctype": "User"}, user=frappe.session.user - ) + columns, result = custom_report.run_query_report(user=frappe.session.user) self.assertListEqual(["email"], [column.get("fieldname") for column in columns]) admin_dict = frappe.core.utils.find(result, lambda d: d["name"] == "Administrator") diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index bb845cae95..3f906d8f12 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -15,12 +15,13 @@ from frappe.model.utils import render_include from frappe.modules import get_module_path, scrub from frappe.monitor import add_data_to_monitor from frappe.permissions import get_role_permissions -from frappe.utils import cint, cstr, flt, format_duration, get_html_format +from frappe.utils import cint, cstr, flt, format_duration, get_html_format, sbool def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) doc.custom_columns = [] + doc.custom_filters = [] if doc.report_type == "Custom Report": custom_report_doc = doc @@ -30,7 +31,8 @@ def get_report_doc(report_name): if custom_report_doc.json: data = json.loads(custom_report_doc.json) if data: - doc.custom_columns = data["columns"] + doc.custom_columns = data.get("columns") + doc.custom_filters = data.get("filters") doc.is_custom_report = True if not doc.is_permitted(): @@ -182,6 +184,7 @@ def run( custom_columns=None, is_tree=False, parent_field=None, + are_default_filters=True, ): report = get_report_doc(report_name) if not user: @@ -194,6 +197,9 @@ def run( result = None + if sbool(are_default_filters) and report.custom_filters: + filters = report.custom_filters + if report.prepared_report and not ignore_prepared_report and not custom_columns: if filters: if isinstance(filters, str): @@ -209,6 +215,9 @@ def run( result["add_total_row"] = report.add_total_row and not result.get("skip_total_row", False) + if sbool(are_default_filters) and report.custom_filters: + result["custom_filters"] = report.custom_filters + return result @@ -463,7 +472,7 @@ def get_data_for_custom_report(columns): @frappe.whitelist() -def save_report(reference_report, report_name, columns): +def save_report(reference_report, report_name, columns, filters): report_doc = get_report_doc(reference_report) docname = frappe.db.exists( @@ -479,6 +488,7 @@ def save_report(reference_report, report_name, columns): report = frappe.get_doc("Report", docname) existing_jd = json.loads(report.json) existing_jd["columns"] = json.loads(columns) + existing_jd["filters"] = json.loads(filters) report.update({"json": json.dumps(existing_jd, separators=(",", ":"))}) report.save() frappe.msgprint(_("Report updated successfully")) @@ -489,7 +499,7 @@ def save_report(reference_report, report_name, columns): { "doctype": "Report", "report_name": report_name, - "json": f'{{"columns":{columns}}}', + "json": f'{{"columns":{columns},"filters":{filters}}}', "ref_doctype": report_doc.ref_doctype, "is_standard": "No", "report_type": "Custom Report", diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index d877f47f21..ee255032bb 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -542,7 +542,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (this.prepared_report) { this.reset_report_view(); } else if (!this._no_refresh) { - this.refresh(); + this.refresh(true); } } }; @@ -598,10 +598,25 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.page.clear_fields(); } - refresh() { + refresh(have_filters_changed) { this.toggle_message(true); this.toggle_report(false); let filters = this.get_filter_values(true); + + // for custom reports, + // are_default_filters is true if the filters haven't been modified and for all filters, + // the filter value is the default value or there's no default value for the filter and the current value is empty. + // are_default_filters is false otherwise. + + let are_default_filters = this.filters + .map((filter) => { + return ( + !have_filters_changed && + (filter.default === filter.value || (!filter.default && !filter.value)) + ); + }) + .every((res) => res === true); + this.show_loading_screen(); // only one refresh at a time @@ -624,6 +639,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { filters: filters, is_tree: this.report_settings.tree, parent_field: this.report_settings.parent_field, + are_default_filters: are_default_filters, }, callback: resolve, always: () => this.page.btn_secondary.prop("disabled", false), @@ -636,6 +652,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.execution_time = data.execution_time || 0.1; + if (data.custom_filters) { + this.set_filters(data.custom_filters); + this.previous_filters = data.custom_filters; + } + if (data.prepared_report) { this.prepared_report = true; this.prepared_report_document = data.doc; @@ -1715,6 +1736,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { reference_report: this.report_name, report_name: values.report_name, columns: this.get_visible_columns(), + filters: this.get_filter_values(), }, callback: function (r) { this.show_save = false; From 652202132d4e1d86fe12585541ccee65385c4402 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 13 Apr 2023 14:37:28 +0200 Subject: [PATCH 21/25] fix: remove guest permission from language (#20677) * fix: remove guest permission from language * fix: allow "All" to select a Language * fix: allow "All" to read a Language --- frappe/core/doctype/language/language.json | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/frappe/core/doctype/language/language.json b/frappe/core/doctype/language/language.json index 7e9bbb1038..c9110bb998 100644 --- a/frappe/core/doctype/language/language.json +++ b/frappe/core/doctype/language/language.json @@ -51,7 +51,7 @@ "icon": "fa fa-globe", "in_create": 1, "links": [], - "modified": "2022-08-14 18:54:03.490836", + "modified": "2023-04-13 13:48:38.127995", "modified_by": "Administrator", "module": "Core", "name": "Language", @@ -66,13 +66,8 @@ "write": 1 }, { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Guest", - "share": 1 + "role": "All", + "read": 1 } ], "search_fields": "language_name", From 76e576c83ec4a686998a356e6cca8f91b79110c0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 14 Apr 2023 09:07:00 +0530 Subject: [PATCH 22/25] test: fix print view test from lang --- frappe/tests/test_website.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 7af2bfda8e..01f6e4f7cc 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -236,6 +236,7 @@ class TestWebsite(FrappeTestCase): def test_printview_page(self): frappe.db.value_cache[("DocType", "Language", "name")] = (("Language",),) + frappe.set_user("Administrator") content = get_response_content("/Language/ru") self.assertIn('