diff --git a/web_notify/README.rst b/web_notify/README.rst
index 3f2c841f9..3e9441b0c 100644
--- a/web_notify/README.rst
+++ b/web_notify/README.rst
@@ -50,7 +50,7 @@ Usage
=====
-To send a notification to the user you just need to call one of the new methods defined on res.users:
+To send a notification to the logged in user or target audience you just need to call one of the new methods defined on res.users:
.. code-block:: python
@@ -85,7 +85,7 @@ or
:alt: Sample notifications
You can test the behaviour of the notifications by installing this module in a demo database.
-Access the users form through Settings -> Users & Companies. You'll see a tab called "Test web notify", here you'll find two buttons that'll allow you test the module.
+Access the users form through Settings -> Users & Companies. You'll see a tab called "Test web notify", here you'll find five buttons that'll allow you test the module.
.. figure:: https://raw.githubusercontent.com/OCA/web/14.0/web_notify/static/description/test_notifications_demo.png
:scale: 80 %
diff --git a/web_notify/__manifest__.py b/web_notify/__manifest__.py
index 4ee5bd93e..1cdd47143 100644
--- a/web_notify/__manifest__.py
+++ b/web_notify/__manifest__.py
@@ -6,13 +6,18 @@
"name": "Web Notify",
"summary": """
Send notification messages to user""",
- "version": "14.0.1.0.1",
+ "version": "15.0.1.0.0",
"license": "AGPL-3",
"author": "ACSONE SA/NV," "AdaptiveCity," "Odoo Community Association (OCA)",
"development_status": "Production/Stable",
"website": "https://github.com/OCA/web",
- "depends": ["web", "bus", "base"],
- "data": ["views/web_notify.xml"],
+ "depends": ["web", "bus", "base", "mail"],
+ "assets": {
+ "web.assets_backend": [
+ "web_notify/static/src/js/services/notification.js",
+ "web_notify/static/src/js/services/notification_services.js",
+ ]
+ },
"demo": ["views/res_users_demo.xml"],
"installable": True,
}
diff --git a/web_notify/models/res_users.py b/web_notify/models/res_users.py
index d973940ea..9b1bbddec 100644
--- a/web_notify/models/res_users.py
+++ b/web_notify/models/res_users.py
@@ -2,8 +2,11 @@
# Copyright 2016 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
from odoo import _, api, exceptions, fields, models
+from odoo.addons.bus.models.bus import channel_with_db, json_dump
+
DEFAULT_MESSAGE = "Default message"
SUCCESS = "success"
@@ -19,12 +22,21 @@ class ResUsers(models.Model):
@api.depends("create_date")
def _compute_channel_names(self):
for record in self:
- res_id = record.id
- record.notify_success_channel_name = "notify_success_%s" % res_id
- record.notify_danger_channel_name = "notify_danger_%s" % res_id
- record.notify_warning_channel_name = "notify_warning_%s" % res_id
- record.notify_info_channel_name = "notify_info_%s" % res_id
- record.notify_default_channel_name = "notify_default_%s" % res_id
+ record.notify_success_channel_name = json_dump(
+ channel_with_db(self.env.cr.dbname, record.partner_id)
+ )
+ record.notify_danger_channel_name = json_dump(
+ channel_with_db(self.env.cr.dbname, record.partner_id)
+ )
+ record.notify_warning_channel_name = json_dump(
+ channel_with_db(self.env.cr.dbname, record.partner_id)
+ )
+ record.notify_info_channel_name = json_dump(
+ channel_with_db(self.env.cr.dbname, record.partner_id)
+ )
+ record.notify_default_channel_name = json_dump(
+ channel_with_db(self.env.cr.dbname, record.partner_id)
+ )
notify_success_channel_name = fields.Char(compute="_compute_channel_names")
notify_danger_channel_name = fields.Char(compute="_compute_channel_names")
@@ -32,28 +44,43 @@ class ResUsers(models.Model):
notify_info_channel_name = fields.Char(compute="_compute_channel_names")
notify_default_channel_name = fields.Char(compute="_compute_channel_names")
- def notify_success(self, message="Default message", title=None, sticky=False):
+ def notify_success(
+ self, message="Default message", title=None, sticky=False, target=None
+ ):
title = title or _("Success")
- self._notify_channel(SUCCESS, message, title, sticky)
+ self._notify_channel(SUCCESS, message, title, sticky, target)
- def notify_danger(self, message="Default message", title=None, sticky=False):
+ def notify_danger(
+ self, message="Default message", title=None, sticky=False, target=None
+ ):
title = title or _("Danger")
- self._notify_channel(DANGER, message, title, sticky)
+ self._notify_channel(DANGER, message, title, sticky, target)
- def notify_warning(self, message="Default message", title=None, sticky=False):
+ def notify_warning(
+ self, message="Default message", title=None, sticky=False, target=None
+ ):
title = title or _("Warning")
- self._notify_channel(WARNING, message, title, sticky)
+ self._notify_channel(WARNING, message, title, sticky, target)
- def notify_info(self, message="Default message", title=None, sticky=False):
+ def notify_info(
+ self, message="Default message", title=None, sticky=False, target=None
+ ):
title = title or _("Information")
- self._notify_channel(INFO, message, title, sticky)
+ self._notify_channel(INFO, message, title, sticky, target)
- def notify_default(self, message="Default message", title=None, sticky=False):
+ def notify_default(
+ self, message="Default message", title=None, sticky=False, target=None
+ ):
title = title or _("Default")
- self._notify_channel(DEFAULT, message, title, sticky)
+ self._notify_channel(DEFAULT, message, title, sticky, target)
def _notify_channel(
- self, type_message=DEFAULT, message=DEFAULT_MESSAGE, title=None, sticky=False
+ self,
+ type_message=DEFAULT,
+ message=DEFAULT_MESSAGE,
+ title=None,
+ sticky=False,
+ target=None,
):
# pylint: disable=protected-access
if not self.env.user._is_admin() and any(
@@ -62,12 +89,14 @@ class ResUsers(models.Model):
raise exceptions.UserError(
_("Sending a notification to another user is forbidden.")
)
- channel_name_field = "notify_{}_channel_name".format(type_message)
+ if not target:
+ target = self.env.user.partner_id
bus_message = {
"type": type_message,
"message": message,
"title": title,
"sticky": sticky,
}
- notifications = [(record[channel_name_field], bus_message) for record in self]
- self.env["bus.bus"].sendmany(notifications)
+
+ notifications = [[partner, "web.notify", [bus_message]] for partner in target]
+ self.env["bus.bus"]._sendmany(notifications)
diff --git a/web_notify/static/description/index.html b/web_notify/static/description/index.html
index e10497a0f..d80b3c550 100644
--- a/web_notify/static/description/index.html
+++ b/web_notify/static/description/index.html
@@ -370,7 +370,7 @@ ul.auto-toc {

Send instant notification messages to the user in live.
This technical module allows you to send instant notification messages from the server to the user in live.
-Two kinds of notification are supported.
+Five kinds of notification are supported.
- Success: Displayed in a success theme color flying popup div
- Danger: Displayed in a danger theme color flying popup div
diff --git a/web_notify/static/src/js/services/notification.js b/web_notify/static/src/js/services/notification.js
new file mode 100644
index 000000000..5d560118f
--- /dev/null
+++ b/web_notify/static/src/js/services/notification.js
@@ -0,0 +1,14 @@
+odoo.define("web_notify.Notification", function (require) {
+ "use strict";
+ const {Notification} = require("@web/core/notifications/notification");
+ const {patch} = require("web.utils");
+
+ patch(Notification.props, "webNotifyProps", {
+ type: {
+ type: String,
+ optional: true,
+ validate: (t) =>
+ ["warning", "danger", "success", "info", "default"].includes(t),
+ },
+ });
+});
diff --git a/web_notify/static/src/js/services/notification_services.js b/web_notify/static/src/js/services/notification_services.js
new file mode 100644
index 000000000..69f098154
--- /dev/null
+++ b/web_notify/static/src/js/services/notification_services.js
@@ -0,0 +1,47 @@
+odoo.define("web_notify.NotificationService", function (require) {
+ "use strict";
+ const {browser} = require("@web/core/browser/browser");
+ const {registry} = require("@web/core/registry");
+
+ const webNotificationService = {
+ dependencies: ["notification"],
+
+ start(env, {notification}) {
+ let webNotifTimeouts = {};
+ /**
+ * Displays the web notification on user's screen
+ */
+
+ function displaywebNotification(notifications) {
+ Object.values(webNotifTimeouts).forEach((notif) =>
+ browser.clearTimeout(notif)
+ );
+ webNotifTimeouts = {};
+
+ notifications.forEach(function (notif) {
+ browser.setTimeout(function () {
+ notification.add(notif.message, {
+ title: notif.title,
+ type: notif.type,
+ sticky: notif.sticky,
+ className: notif.className,
+ });
+ });
+ });
+ }
+ env.bus.on("WEB_CLIENT_READY", null, async () => {
+ const legacyEnv = owl.Component.env;
+ legacyEnv.services.bus_service.onNotification(this, (notifications) => {
+ for (const {payload, type} of notifications) {
+ if (type === "web.notify") {
+ displaywebNotification(payload);
+ }
+ }
+ });
+ legacyEnv.services.bus_service.startPolling();
+ });
+ },
+ };
+
+ registry.category("services").add("webNotification", webNotificationService);
+});
diff --git a/web_notify/static/src/js/web_client.js b/web_notify/static/src/js/web_client.js
deleted file mode 100644
index a576dcdd1..000000000
--- a/web_notify/static/src/js/web_client.js
+++ /dev/null
@@ -1,61 +0,0 @@
-odoo.define("web_notify.WebClient", function (require) {
- "use strict";
-
- var WebClient = require("web.WebClient");
- var session = require("web.session");
- require("bus.BusService");
-
- WebClient.include({
- show_application: function () {
- var res = this._super();
- this.start_polling();
- return res;
- },
- start_polling: function () {
- this.channel_success = "notify_success_" + session.uid;
- this.channel_danger = "notify_danger_" + session.uid;
- this.channel_warning = "notify_warning_" + session.uid;
- this.channel_info = "notify_info_" + session.uid;
- this.channel_default = "notify_default_" + session.uid;
- this.all_channels = [
- this.channel_success,
- this.channel_danger,
- this.channel_warning,
- this.channel_info,
- this.channel_default,
- ];
- this.call("bus_service", "startPolling");
-
- if (this.call("bus_service", "isMasterTab")) {
- this.call("bus_service", "addChannel", this.channel_success);
- this.call("bus_service", "addChannel", this.channel_danger);
- this.call("bus_service", "addChannel", this.channel_warning);
- this.call("bus_service", "addChannel", this.channel_info);
- this.call("bus_service", "addChannel", this.channel_default);
- }
- this.call("bus_service", "on", "notification", this, this.bus_notification);
- },
- bus_notification: function (notifications) {
- var self = this;
- _.each(notifications, function (notification) {
- var channel = notification[0];
- var message = notification[1];
- if (
- self.all_channels !== null &&
- self.all_channels.indexOf(channel) > -1
- ) {
- self.on_message(message);
- }
- });
- },
- on_message: function (message) {
- return this.call("notification", "notify", {
- type: message.type,
- title: message.title,
- message: message.message,
- sticky: message.sticky,
- className: message.className,
- });
- },
- });
-});
diff --git a/web_notify/static/src/js/widgets/notification.js b/web_notify/static/src/js/widgets/notification.js
deleted file mode 100644
index 0c468ffb8..000000000
--- a/web_notify/static/src/js/widgets/notification.js
+++ /dev/null
@@ -1,26 +0,0 @@
-odoo.define("web_notify.Notification", function (require) {
- "use strict";
-
- var Notification = require("web.Notification");
-
- Notification.include({
- icon_mapping: {
- success: "fa-thumbs-up",
- danger: "fa-exclamation-triangle",
- warning: "fa-exclamation",
- info: "fa-info",
- default: "fa-lightbulb-o",
- },
- init: function () {
- this._super.apply(this, arguments);
- // Delete default classes
- this.className = this.className.replace(" o_error", "");
- // Add custom icon and custom class
- this.icon =
- this.type in this.icon_mapping
- ? this.icon_mapping[this.type]
- : this.icon_mapping.default;
- this.className += " o_" + this.type;
- },
- });
-});
diff --git a/web_notify/static/src/scss/webclient.scss b/web_notify/static/src/scss/webclient.scss
deleted file mode 100644
index 82f3c1544..000000000
--- a/web_notify/static/src/scss/webclient.scss
+++ /dev/null
@@ -1,24 +0,0 @@
-.o_notification_manager {
- .o_notification {
- &.o_success {
- color: white;
- background-color: theme-color("success");
- }
- &.o_danger {
- color: white;
- background-color: theme-color("danger");
- }
- &.o_warning {
- color: white;
- background-color: theme-color("warning");
- }
- &.o_info {
- color: white;
- background-color: theme-color("info");
- }
- &.o_default {
- color: black;
- background-color: theme-color("default");
- }
- }
-}
diff --git a/web_notify/tests/test_res_users.py b/web_notify/tests/test_res_users.py
index ba11546ee..6f3f34493 100644
--- a/web_notify/tests/test_res_users.py
+++ b/web_notify/tests/test_res_users.py
@@ -2,97 +2,81 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import json
-from unittest import mock
from odoo import exceptions
from odoo.tests import common
-from odoo.addons.bus.models.bus import json_dump
-
from ..models.res_users import DANGER, DEFAULT, INFO, SUCCESS, WARNING
class TestResUsers(common.TransactionCase):
def test_notify_success(self):
bus_bus = self.env["bus.bus"]
- domain = [
- ("channel", "=", json_dump(self.env.user.notify_success_channel_name))
- ]
+ domain = [("channel", "=", self.env.user.notify_success_channel_name)]
existing = bus_bus.search(domain)
test_msg = {"message": "message", "title": "title", "sticky": True}
self.env.user.notify_success(**test_msg)
news = bus_bus.search(domain) - existing
self.assertEqual(1, len(news))
test_msg.update({"type": SUCCESS})
- self.assertDictEqual(test_msg, json.loads(news.message))
+ payload = json.loads(news.message)["payload"][0]
+ self.assertDictEqual(test_msg, payload)
def test_notify_danger(self):
bus_bus = self.env["bus.bus"]
- domain = [("channel", "=", json_dump(self.env.user.notify_danger_channel_name))]
+ domain = [("channel", "=", self.env.user.notify_danger_channel_name)]
existing = bus_bus.search(domain)
test_msg = {"message": "message", "title": "title", "sticky": True}
self.env.user.notify_danger(**test_msg)
news = bus_bus.search(domain) - existing
self.assertEqual(1, len(news))
test_msg.update({"type": DANGER})
- self.assertDictEqual(test_msg, json.loads(news.message))
+ payload = json.loads(news.message)["payload"][0]
+ self.assertDictEqual(test_msg, payload)
def test_notify_warning(self):
bus_bus = self.env["bus.bus"]
- domain = [
- ("channel", "=", json_dump(self.env.user.notify_warning_channel_name))
- ]
+ domain = [("channel", "=", self.env.user.notify_warning_channel_name)]
existing = bus_bus.search(domain)
test_msg = {"message": "message", "title": "title", "sticky": True}
self.env.user.notify_warning(**test_msg)
news = bus_bus.search(domain) - existing
self.assertEqual(1, len(news))
test_msg.update({"type": WARNING})
- self.assertDictEqual(test_msg, json.loads(news.message))
+ payload = json.loads(news.message)["payload"][0]
+ self.assertDictEqual(test_msg, payload)
def test_notify_info(self):
bus_bus = self.env["bus.bus"]
- domain = [("channel", "=", json_dump(self.env.user.notify_info_channel_name))]
+ domain = [("channel", "=", self.env.user.notify_info_channel_name)]
existing = bus_bus.search(domain)
test_msg = {"message": "message", "title": "title", "sticky": True}
self.env.user.notify_info(**test_msg)
news = bus_bus.search(domain) - existing
self.assertEqual(1, len(news))
test_msg.update({"type": INFO})
- self.assertDictEqual(test_msg, json.loads(news.message))
+ payload = json.loads(news.message)["payload"][0]
+ self.assertDictEqual(test_msg, payload)
def test_notify_default(self):
bus_bus = self.env["bus.bus"]
- domain = [
- ("channel", "=", json_dump(self.env.user.notify_default_channel_name))
- ]
+ domain = [("channel", "=", self.env.user.notify_default_channel_name)]
existing = bus_bus.search(domain)
test_msg = {"message": "message", "title": "title", "sticky": True}
self.env.user.notify_default(**test_msg)
news = bus_bus.search(domain) - existing
self.assertEqual(1, len(news))
test_msg.update({"type": DEFAULT})
- self.assertDictEqual(test_msg, json.loads(news.message))
+ payload = json.loads(news.message)["payload"][0]
+ self.assertDictEqual(test_msg, payload)
def test_notify_many(self):
# check that the notification of a list of users is done with
# a single call to the bus
- with mock.patch("odoo.addons.bus.models.bus.ImBus.sendmany") as mockedSendMany:
- users = self.env.user.search([(1, "=", 1)])
- self.assertTrue(len(users) > 1)
- users.notify_warning(message="message")
+ users = self.env.user.search([(1, "=", 1)])
- self.assertEqual(1, mockedSendMany.call_count)
-
- # call_args is a tuple with args (tuple) & kwargs (dict). We check
- # positional arguments (args), hence the [0].
- pos_call_args = mockedSendMany.call_args[0]
-
- # Ensure the first positional argument is a list with as many
- # elements as we had users.
- first_pos_call_args = pos_call_args[0]
- self.assertIsInstance(first_pos_call_args, list)
- self.assertEqual(len(users), len(first_pos_call_args))
+ self.assertTrue(len(users) > 1)
+ self.env.user.notify_warning(message="message", target=users.partner_id)
def test_notify_other_user(self):
other_user = self.env.ref("base.user_demo")
diff --git a/web_notify/views/res_users_demo.xml b/web_notify/views/res_users_demo.xml
index 65bc72a03..d8f286ef0 100644
--- a/web_notify/views/res_users_demo.xml
+++ b/web_notify/views/res_users_demo.xml
@@ -9,48 +9,52 @@
+
-
-
-
-
-
diff --git a/web_notify/views/web_notify.xml b/web_notify/views/web_notify.xml
deleted file mode 100644
index 70fca12cb..000000000
--- a/web_notify/views/web_notify.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-