forked from Techsystech/web
[12.0] web_notify: improve popup UI (#1231)
* [ADD]: all available bootstrap notifications (success/danger/warning/info/default) * [IMP] use black color for text for default notification. * [FIX] reverted require string for `bus.Longpolling` and rename `on_message_received` to `on_message` to prevent collisions.12.0
parent
1d61efba5e
commit
9b905c8e95
|
@ -23,11 +23,11 @@ Web Notify
|
|||
:target: https://runbot.odoo-community.org/runbot/162/12.0
|
||||
:alt: Try me on Runbot
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|
||||
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.
|
||||
This technical module allows you to send instant notification messages from the server to the user in live.
|
||||
Two kinds of notification are supported.
|
||||
|
||||
* Warning: Displayed in a red flying popup div
|
||||
|
@ -50,13 +50,13 @@ Usage
|
|||
To send a notification to the user you just need to call one of the new methods defined on res.users:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
self.env.user.notify_info(message='My information message')
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
self.env.user.notify_warning(message='My marning message')
|
||||
|
||||
.. figure:: https://raw.githubusercontent.com/OCA/web/12.0/web_notify/static/description/notifications_screenshot.png
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# pylint: disable=missing-docstring
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import models
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# pylint: disable=missing-docstring
|
||||
# Copyright 2016 ACSONE SA/NV
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
|
@ -6,7 +7,6 @@
|
|||
'summary': """
|
||||
Send notification messages to user""",
|
||||
'version': '12.0.1.0.0',
|
||||
'description': 'Web Notify',
|
||||
'license': 'AGPL-3',
|
||||
'author': 'ACSONE SA/NV,'
|
||||
'AdaptiveCity,'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# pylint: disable=missing-docstring
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import res_users
|
||||
|
|
|
@ -1,46 +1,87 @@
|
|||
# pylint: disable=missing-docstring
|
||||
# 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 import _, api, exceptions, fields, models
|
||||
|
||||
DEFAULT_MESSAGE = "Default message"
|
||||
|
||||
SUCCESS = "success"
|
||||
DANGER = "danger"
|
||||
WARNING = "warning"
|
||||
INFO = "info"
|
||||
DEFAULT = "default"
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
_inherit = "res.users"
|
||||
|
||||
@api.depends('create_date')
|
||||
@api.depends("create_date")
|
||||
def _compute_channel_names(self):
|
||||
for record in self:
|
||||
res_id = record.id
|
||||
record.notify_info_channel_name = 'notify_info_%s' % res_id
|
||||
record.notify_warning_channel_name = 'notify_warning_%s' % res_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
|
||||
|
||||
notify_info_channel_name = fields.Char(
|
||||
compute='_compute_channel_names')
|
||||
notify_warning_channel_name = fields.Char(
|
||||
compute='_compute_channel_names')
|
||||
notify_success_channel_name = fields.Char(compute="_compute_channel_names")
|
||||
notify_danger_channel_name = fields.Char(compute="_compute_channel_names")
|
||||
notify_warning_channel_name = fields.Char(compute="_compute_channel_names")
|
||||
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
|
||||
):
|
||||
title = title or _("Success")
|
||||
self._notify_channel(SUCCESS, message, title, sticky)
|
||||
|
||||
def notify_danger(
|
||||
self, message="Default message", title=None, sticky=False
|
||||
):
|
||||
title = title or _("Danger")
|
||||
self._notify_channel(DANGER, message, title, sticky)
|
||||
|
||||
def notify_warning(
|
||||
self, message="Default message", title=None, sticky=False
|
||||
):
|
||||
title = title or _("Warning")
|
||||
self._notify_channel(WARNING, message, title, sticky)
|
||||
|
||||
def notify_info(self, message="Default message", title=None, sticky=False):
|
||||
title = title or _('Information')
|
||||
self._notify_channel(
|
||||
'notify_info_channel_name', message, title, sticky)
|
||||
title = title or _("Information")
|
||||
self._notify_channel(INFO, message, title, sticky)
|
||||
|
||||
def notify_warning(self, message="Default message",
|
||||
title=None, sticky=False):
|
||||
title = title or _('Warning')
|
||||
self._notify_channel(
|
||||
'notify_warning_channel_name', message, title, sticky)
|
||||
def notify_default(
|
||||
self, message="Default message", title=None, sticky=False
|
||||
):
|
||||
title = title or _("Default")
|
||||
self._notify_channel(DEFAULT, message, title, sticky)
|
||||
|
||||
def _notify_channel(self, channel_name_field, message, title, sticky):
|
||||
if (not self.env.user._is_admin()
|
||||
and any(user.id != self.env.uid for user in self)):
|
||||
def _notify_channel(
|
||||
self,
|
||||
type_message=DEFAULT,
|
||||
message=DEFAULT_MESSAGE,
|
||||
title=None,
|
||||
sticky=False,
|
||||
):
|
||||
# pylint: disable=protected-access
|
||||
if not self.env.user._is_admin() and any(
|
||||
user.id != self.env.uid for user in self
|
||||
):
|
||||
raise exceptions.UserError(
|
||||
_('Sending a notification to another user is forbidden.')
|
||||
_("Sending a notification to another user is forbidden.")
|
||||
)
|
||||
channel_name_field = "notify_{}_channel_name".format(type_message)
|
||||
bus_message = {
|
||||
'message': message,
|
||||
'title': title,
|
||||
'sticky': sticky
|
||||
"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 = [
|
||||
(record[channel_name_field], bus_message) for record in self
|
||||
]
|
||||
self.env["bus.bus"].sendmany(notifications)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
* Laurent Mignon <laurent.mignon@acsone.eu>
|
||||
* Serpent Consulting Services Pvt. Ltd.<jay.vora@serpentcs.com>
|
||||
* Aitor Bouzas <aitor.bouzas@adaptivecity.com>
|
||||
* Aitor Bouzas <aitor.bouzas@adaptivecity.com>
|
||||
* Shepilov Vladislav <shepilov.v@protonmail.com>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
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.
|
||||
This technical module allows you to send instant notification messages from the server to the user in live.
|
||||
Two kinds of notification are supported.
|
||||
|
||||
* Warning: Displayed in a red flying popup div
|
||||
* Information: Displayed in a light yellow flying popup div
|
||||
* Success: Displayed in a `success` theme color flying popup div
|
||||
* Danger: Displayed in a `danger` theme color flying popup div
|
||||
* Warning: Displayed in a `warning` theme color flying popup div
|
||||
* Information: Displayed in a `info` theme color flying popup div
|
||||
* Default: Displayed in a `default` theme color flying popup div
|
||||
|
|
|
@ -1 +1 @@
|
|||
This module is based on the Instant Messaging Bus. To work properly, the server must be launched in gevent mode.
|
||||
This module is based on the Instant Messaging Bus. To work properly, the server must be launched in gevent mode.
|
||||
|
|
|
@ -2,14 +2,32 @@
|
|||
To send a notification to the user you just need to call one of the new methods defined on res.users:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.env.user.notify_info(message='My information message')
|
||||
|
||||
or
|
||||
self.env.user.notify_success(message='My success message')
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.env.user.notify_warning(message='My marning message')
|
||||
|
||||
self.env.user.notify_danger(message='My danger message')
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.env.user.notify_warning(message='My warning message')
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.env.user.notify_info(message='My information message')
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
self.env.user.notify_default(message='My default message')
|
||||
|
||||
.. figure:: static/description/notifications_screenshot.png
|
||||
:scale: 80 %
|
||||
|
|
|
@ -1,44 +1,53 @@
|
|||
odoo.define('web_notify.WebClient', function (require) {
|
||||
"use strict";
|
||||
"use strict";
|
||||
|
||||
var WebClient = require('web.WebClient');
|
||||
var base_bus = require('bus.Longpolling');
|
||||
var session = require('web.session');
|
||||
require('bus.BusService');
|
||||
var WebClient = require('web.WebClient');
|
||||
var base_bus = require('bus.Longpolling');
|
||||
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_warning = 'notify_warning_' + session.uid;
|
||||
this.channel_info = 'notify_info_' + session.uid;
|
||||
this.call('bus_service', 'addChannel', this.channel_warning);
|
||||
this.call('bus_service', 'addChannel', this.channel_info);
|
||||
this.call('bus_service', 'on', 'notification', this, this.bus_notification);
|
||||
this.call('bus_service', 'startPolling');
|
||||
},
|
||||
bus_notification: function(notifications) {
|
||||
var self = this;
|
||||
_.each(notifications, function (notification) {
|
||||
var channel = notification[0];
|
||||
var message = notification[1];
|
||||
if (channel === self.channel_warning) {
|
||||
self.on_message_warning(message);
|
||||
} else if (channel === self.channel_info) {
|
||||
self.on_message_info(message);
|
||||
}
|
||||
});
|
||||
},
|
||||
on_message_warning: function(message){
|
||||
this.do_warn(message.title, message.message, message.sticky);
|
||||
},
|
||||
on_message_info: function(message){
|
||||
this.do_notify(message.title, message.message, message.sticky);
|
||||
}
|
||||
});
|
||||
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.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);
|
||||
this.call('bus_service', 'startPolling');
|
||||
},
|
||||
bus_notification: function (notifications) {
|
||||
var self = this;
|
||||
_.each(notifications, function (notification) {
|
||||
// Not used: var channel = notification[0];
|
||||
var message = notification[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,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
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;
|
||||
},
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
.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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +1,106 @@
|
|||
# Copyright 2016 ACSONE SA/NV
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import exceptions
|
||||
from odoo.tests import common
|
||||
from odoo.addons.bus.models.bus import json_dump
|
||||
import json
|
||||
|
||||
import mock
|
||||
from odoo import exceptions
|
||||
from odoo.addons.bus.models.bus import json_dump
|
||||
from ..models.res_users import SUCCESS, DANGER, WARNING, INFO, DEFAULT
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class TestResUsers(common.TransactionCase):
|
||||
|
||||
def test_notify_info(self):
|
||||
bus_bus = self.env['bus.bus']
|
||||
def test_notify_success(self):
|
||||
bus_bus = self.env["bus.bus"]
|
||||
domain = [
|
||||
('channel', '=',
|
||||
json_dump(self.env.user.notify_info_channel_name))
|
||||
(
|
||||
"channel",
|
||||
"=",
|
||||
json_dump(self.env.user.notify_success_channel_name),
|
||||
)
|
||||
]
|
||||
existing = bus_bus.search(domain)
|
||||
test_msg = {'message': 'message', 'title': 'title', 'sticky': True}
|
||||
self.env.user.notify_info(**test_msg)
|
||||
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))
|
||||
self.assertEqual(test_msg, json.loads(news.message))
|
||||
test_msg.update({"type": SUCCESS})
|
||||
self.assertDictEqual(test_msg, json.loads(news.message))
|
||||
|
||||
def test_notify_warning(self):
|
||||
bus_bus = self.env['bus.bus']
|
||||
def test_notify_danger(self):
|
||||
bus_bus = self.env["bus.bus"]
|
||||
domain = [
|
||||
('channel', '=',
|
||||
json_dump(self.env.user.notify_warning_channel_name))
|
||||
(
|
||||
"channel",
|
||||
"=",
|
||||
json_dump(self.env.user.notify_danger_channel_name),
|
||||
)
|
||||
]
|
||||
existing = bus_bus.search(domain)
|
||||
test_msg = {'message': 'message', 'title': 'title', 'sticky': True}
|
||||
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))
|
||||
|
||||
def test_notify_warning(self):
|
||||
bus_bus = self.env["bus.bus"]
|
||||
domain = [
|
||||
(
|
||||
"channel",
|
||||
"=",
|
||||
json_dump(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))
|
||||
self.assertEqual(test_msg, json.loads(news.message))
|
||||
test_msg.update({"type": WARNING})
|
||||
self.assertDictEqual(test_msg, json.loads(news.message))
|
||||
|
||||
def test_notify_info(self):
|
||||
bus_bus = self.env["bus.bus"]
|
||||
domain = [
|
||||
("channel", "=", json_dump(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))
|
||||
|
||||
def test_notify_default(self):
|
||||
bus_bus = self.env["bus.bus"]
|
||||
domain = [
|
||||
(
|
||||
"channel",
|
||||
"=",
|
||||
json_dump(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))
|
||||
|
||||
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:
|
||||
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.notify_warning(message="message")
|
||||
|
||||
self.assertEqual(1, mockedSendMany.call_count)
|
||||
|
||||
|
@ -58,11 +115,11 @@ class TestResUsers(common.TransactionCase):
|
|||
self.assertEqual(len(users), len(first_pos_call_args))
|
||||
|
||||
def test_notify_other_user(self):
|
||||
other_user = self.env.ref('base.user_demo')
|
||||
other_user_model = self.env['res.users'].sudo(other_user)
|
||||
other_user = self.env.ref("base.user_demo")
|
||||
other_user_model = self.env["res.users"].sudo(other_user)
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
other_user_model.browse(self.env.uid).notify_info(message='hello')
|
||||
other_user_model.browse(self.env.uid).notify_info(message="hello")
|
||||
|
||||
def test_notify_admin_allowed_other_user(self):
|
||||
other_user = self.env.ref('base.user_demo')
|
||||
other_user.notify_info(message='hello')
|
||||
other_user = self.env.ref("base.user_demo")
|
||||
other_user.notify_info(message="hello")
|
||||
|
|
|
@ -11,9 +11,15 @@
|
|||
<page string="Test web notify" name="test_web_notify">
|
||||
<group>
|
||||
<group>
|
||||
<button name="notify_info"
|
||||
<button name="notify_success"
|
||||
type="object"
|
||||
string="Test info notification"
|
||||
string="Test success notification"
|
||||
class="oe_highlight"/>
|
||||
</group>
|
||||
<group>
|
||||
<button name="notify_danger"
|
||||
type="object"
|
||||
string="Test danger notification"
|
||||
class="oe_highlight"/>
|
||||
</group>
|
||||
<group>
|
||||
|
@ -22,6 +28,18 @@
|
|||
string="Test warning notification"
|
||||
class="oe_highlight"/>
|
||||
</group>
|
||||
<group>
|
||||
<button name="notify_info"
|
||||
type="object"
|
||||
string="Test info notification"
|
||||
class="oe_highlight"/>
|
||||
</group>
|
||||
<group>
|
||||
<button name="notify_default"
|
||||
type="object"
|
||||
string="Test default notification"
|
||||
class="oe_highlight"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="assets_backend" name="web_notify assets" inherit_id="web.assets_backend">
|
||||
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/webclient.scss" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/web_notify/static/src/scss/webclient.scss"/>
|
||||
</link>
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/web_notify/static/src/js/web_client.js"/>
|
||||
<script type="text/javascript" src="/web_notify/static/src/js/widgets/notification.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
|
|
Loading…
Reference in New Issue