diff --git a/frappe/database/operator_map.py b/frappe/database/operator_map.py index c8cb9aa099..218983a318 100644 --- a/frappe/database/operator_map.py +++ b/frappe/database/operator_map.py @@ -8,7 +8,6 @@ import frappe from frappe.database.utils import NestedSetHierarchy from frappe.model.db_query import get_timespan_date_range from frappe.query_builder import Field -from frappe.query_builder.functions import Coalesce from frappe.utils import cstr @@ -51,7 +50,7 @@ def func_in(key: Field, value: list | tuple) -> frappe.qb: value = ["" if v is None else v for v in value] if "" in value: - return Coalesce(key, "").isin(value) + return key.isin(value) | key.isnull() return key.isin(value) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index b76ecff96e..fc68a501aa 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -34,7 +34,6 @@ frappe.ui.Notifications = class Notifications { `) .on("click", (e) => { e.stopImmediatePropagation(); - console.log("what"); frappe.set_route("Form", "Notification Settings", frappe.session.user); }) .appendTo(this.header_actions) @@ -247,15 +246,13 @@ class NotificationsView extends BaseNotificationsView { .tooltip({ delay: { show: 600, hide: 100 }, trigger: "hover" }); this.setup_notification_listeners(); - this.get_notifications_list(this.max_length).then((r) => { - if (!r.message) return; - this.dropdown_items = r.message.notification_logs; - frappe.update_user_info(r.message.user_info); - this.render_notifications_dropdown(); - if (this.settings.seen == 0 && this.dropdown_items.length > 0) { - this.toggle_notification_icon(false); - } - }); + + this.dropdown_items = []; + this.notifications_fetched = false; + + if (this.settings && this.settings.seen == 0) { + this.toggle_notification_icon(false); + } } update_dropdown() { @@ -370,7 +367,7 @@ class NotificationsView extends BaseNotificationsView {
${__("See all Activity")}
`); } else { - this.container.append( + this.container.html( $(`
Generic Empty State @@ -432,25 +429,58 @@ class NotificationsView extends BaseNotificationsView { frappe.realtime.on("indicator_hide", () => { this.toggle_notification_icon(true); }); + + this.parent.on("show.bs.dropdown", () => { + if (!this.notifications_fetched) { + this.container.html(`
+
+
+
+
`); + this.get_notifications_list(this.max_length).then((r) => { + if (r.message && r.message.notification_logs) { + this.dropdown_items = r.message.notification_logs; + frappe.update_user_info(r.message.user_info); + } else { + this.dropdown_items = []; + } + this.render_notifications_dropdown(); + this.notifications_fetched = true; + }); + } + }); } } class EventsView extends BaseNotificationsView { make() { - let today = frappe.datetime.get_today(); - frappe - .xcall( - "frappe.desk.doctype.event.event.get_events", - { - start: today, - end: today, - }, - "GET", - { cache: true } - ) - .then((event_list) => { - this.render_events_html(event_list); - }); + this.events_fetched = false; + + this.parent.on("show.bs.dropdown", () => { + if (this.events_fetched) return; + + this.container.html(`
+
+
+
+
`); + + let today = frappe.datetime.get_today(); + frappe + .xcall( + "frappe.desk.doctype.event.event.get_events", + { + start: today, + end: today, + }, + "GET", + { cache: true } + ) + .then((event_list) => { + this.render_events_html(event_list); + this.events_fetched = true; + }); + }); } render_events_html(event_list) { diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.js b/frappe/public/js/frappe/ui/sidebar/sidebar.js index ff43cd08d6..f07e9fdde5 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.js @@ -484,7 +484,11 @@ frappe.ui.Sidebar = class Sidebar { type: "Button", class: "sidebar-notification hidden", onClick: () => { - this.wrapper.find(".dropdown-notifications").toggleClass("hidden"); + const $dropdown = this.wrapper.find(".dropdown-notifications"); + $dropdown.toggleClass("hidden"); + if (!$dropdown.hasClass("hidden")) { + $dropdown.trigger("show.bs.dropdown"); + } if (frappe.is_mobile()) { this.wrapper.removeClass("expanded"); } diff --git a/frappe/tests/test_query_builder.py b/frappe/tests/test_query_builder.py index 920b06e4af..f0df927238 100644 --- a/frappe/tests/test_query_builder.py +++ b/frappe/tests/test_query_builder.py @@ -525,15 +525,17 @@ class TestOperatorIn(IntegrationTestCase): query = func_in(note.name, [None, "user1"]) sql_str = str(query).lower() - self.assertIn("coalesce", sql_str) + self.assertNotIn("coalesce", sql_str) + self.assertIn("is null", sql_str) self.assertIn("''", sql_str) - def test_func_in_with_empty_string_uses_coalesce(self): + def test_func_in_with_empty_string_uses_or_is_null(self): note = frappe.qb.DocType("Note") query = func_in(note.name, ["", "user1"]) sql_str = str(query).lower() - self.assertIn("coalesce", sql_str) + self.assertNotIn("coalesce", sql_str) + self.assertIn("is null", sql_str) self.assertIn("''", sql_str) def test_func_in_with_mixed_none_and_values(self): @@ -541,7 +543,8 @@ class TestOperatorIn(IntegrationTestCase): query = func_in(note.name, ["val1", None, "val2"]) sql_str = str(query).lower() - self.assertIn("coalesce", sql_str) + self.assertNotIn("coalesce", sql_str) + self.assertIn("is null", sql_str) def test_in_filter_matches_null_and_empty_columns(self): test_doctype = new_doctype( diff --git a/frappe/www/portal.py b/frappe/www/portal.py index 610063cdc2..9aebe54c02 100644 --- a/frappe/www/portal.py +++ b/frappe/www/portal.py @@ -6,6 +6,8 @@ from frappe.model.document import Document from frappe.utils.data import quoted from frappe.www.list import get_list_context, get_list_data +no_cache = 1 + def get_context(context, **dict_params): frappe.local.form_dict.update(dict_params)