Notify subscribed users of new message in chat
This commit is contained in:
1 parent
48dcd6ffdd
commit
fa19c6226f
7 files changed
+174
-13
No files matched your search
@@ -1286,9 +1286,23 @@ ChatMixin
|
||||
A mixin_ to activate a `model-ir.chat.channel` on any
|
||||
:class:`~trytond.model.ModelStorage` record.
|
||||
|
||||
Class methods:
|
||||
|
||||
.. classmethod:: ChatMixin.chat_post(records, message_id[, audience[, n[, \*\*variables]]])
|
||||
|
||||
Posts a message to the `model-ir.chat.channel` of the ``records`` using the
|
||||
XML ID of a `model-ir.message` formatted using
|
||||
:func:`~trytond.i18n.gettext` or :func:`~trytond.i18n.ngettext` if ``n`` is
|
||||
set.
|
||||
|
||||
Instance methods:
|
||||
|
||||
.. method:: ChatMixin.chat_language([audience])
|
||||
|
||||
Returns the language to use to translate the message posted by
|
||||
:meth:`~ChatMixin.chat_post`.
|
||||
|
||||
|
||||
.. _mixin: http://en.wikipedia.org/wiki/Mixin
|
||||
.. _JSON: http://en.wikipedia.org/wiki/Json
|
||||
.. _UNION: http://en.wikipedia.org/wiki/Union_(SQL)#UNION_operator
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
import json
|
||||
|
||||
from sql import Null
|
||||
from sql.conditionals import NullIf
|
||||
|
||||
@@ -10,6 +12,7 @@ from trytond.model import ChatMixin, Check, ModelSQL, Unique, fields
|
||||
from trytond.model.exceptions import ValidationError
|
||||
from trytond.pool import Pool
|
||||
from trytond.rpc import RPC
|
||||
from trytond.tools import firstline
|
||||
from trytond.tools.email_ import (
|
||||
EmailNotValidError, normalize_email, validate_email)
|
||||
from trytond.transaction import Transaction
|
||||
@@ -114,7 +117,9 @@ class Channel(ModelSQL):
|
||||
pool = Pool()
|
||||
Message = pool.get('ir.chat.message')
|
||||
User = pool.get('res.user')
|
||||
user = User(Transaction().user)
|
||||
transaction = Transaction()
|
||||
user = User(transaction.user)
|
||||
ctx_user = User(transaction.context.get('user', user))
|
||||
channel = cls._get_channel(resource)
|
||||
message = Message(
|
||||
channel=channel,
|
||||
@@ -129,7 +134,8 @@ class Channel(ModelSQL):
|
||||
'message': message.as_dict(),
|
||||
})
|
||||
for follower in channel.followers:
|
||||
follower.notify(message)
|
||||
if follower.user != ctx_user:
|
||||
follower.notify(message)
|
||||
|
||||
return message
|
||||
|
||||
@@ -190,6 +196,8 @@ class AuthorMixin:
|
||||
@fields.depends('user', 'email')
|
||||
def on_change_with_author(self, name=None):
|
||||
if self.user:
|
||||
if not self.user.id:
|
||||
return
|
||||
return self.user.name
|
||||
elif self.email:
|
||||
return self.email
|
||||
@@ -253,7 +261,18 @@ class Follower(AuthorMixin, ModelSQL):
|
||||
]))
|
||||
|
||||
def notify(self, message):
|
||||
pass
|
||||
pool = Pool()
|
||||
Notification = pool.get('res.notification')
|
||||
|
||||
if self.user:
|
||||
Notification(
|
||||
user=self.user,
|
||||
label=message.author,
|
||||
description=firstline(message.content),
|
||||
icon='tryton-chat',
|
||||
model=message.channel.resource.__name__,
|
||||
records=json.dumps([message.channel.resource.id])
|
||||
).save()
|
||||
|
||||
|
||||
class Message(AuthorMixin, ModelSQL):
|
||||
|
||||
@@ -1,6 +1,53 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from collections import namedtuple
|
||||
from itertools import groupby
|
||||
|
||||
from trytond.i18n import gettext, ngettext
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
from .descriptors import dualmethod
|
||||
|
||||
ChatMessage = namedtuple(
|
||||
'ChatMessage',
|
||||
['msg_id', 'n', 'variables', 'audience'])
|
||||
|
||||
|
||||
class ChatMixin:
|
||||
__slots__ = ()
|
||||
|
||||
def chat_language(self, audience='internal'):
|
||||
return
|
||||
|
||||
@dualmethod
|
||||
def chat_post(
|
||||
cls, records, message_id,
|
||||
audience='internal', n=None, **variables):
|
||||
transaction = Transaction()
|
||||
for language, recs in groupby(
|
||||
records, key=lambda r: r.chat_language(audience)):
|
||||
with transaction.set_context(language=language):
|
||||
cls.__queue__._chat_dispatch(
|
||||
records,
|
||||
ChatMessage(
|
||||
msg_id=message_id,
|
||||
n=n,
|
||||
variables=variables,
|
||||
audience=audience,
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def _chat_dispatch(cls, records, message):
|
||||
pool = Pool()
|
||||
Channel = pool.get('ir.chat.channel')
|
||||
transaction = Transaction()
|
||||
message = ChatMessage(*message)
|
||||
if message.n is None:
|
||||
msg = gettext(message.msg_id, **message.variables)
|
||||
else:
|
||||
msg = ngettext(message.msg_id, message.n, **message.variables)
|
||||
with transaction.set_user(0):
|
||||
for record in records:
|
||||
Channel.post(record, msg, audience=message.audience)
|
||||
@@ -9,14 +9,14 @@ except ImportError:
|
||||
|
||||
def register():
|
||||
from . import (
|
||||
access, copy_, export_data, field_binary, field_boolean, field_char,
|
||||
field_context, field_date, field_datetime, field_dict, field_float,
|
||||
field_fmany2one, field_function, field_integer, field_many2many,
|
||||
field_many2one, field_multiselection, field_numeric, field_one2many,
|
||||
field_one2one, field_reference, field_selection, field_text,
|
||||
field_time, field_timedelta, history, import_data, mixin, model,
|
||||
model_log, modelsql, modelstorage, modelview, mptt, multivalue, path,
|
||||
resource, rule, tree, trigger, wizard, workflow)
|
||||
access, chat, copy_, export_data, field_binary, field_boolean,
|
||||
field_char, field_context, field_date, field_datetime, field_dict,
|
||||
field_float, field_fmany2one, field_function, field_integer,
|
||||
field_many2many, field_many2one, field_multiselection, field_numeric,
|
||||
field_one2many, field_one2one, field_reference, field_selection,
|
||||
field_text, field_time, field_timedelta, history, import_data, mixin,
|
||||
model, model_log, modelsql, modelstorage, modelview, mptt, multivalue,
|
||||
path, resource, rule, tree, trigger, wizard, workflow)
|
||||
|
||||
access.register('tests')
|
||||
copy_.register('tests')
|
||||
@@ -53,6 +53,7 @@ def register():
|
||||
model_log.register('tests')
|
||||
mptt.register('tests')
|
||||
multivalue.register('tests')
|
||||
chat.register('tests')
|
||||
path.register('tests')
|
||||
resource.register('tests')
|
||||
rule.register('tests')
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ChatMixin, ModelSQL
|
||||
from trytond.pool import Pool
|
||||
|
||||
|
||||
class ChatRoom(ChatMixin, ModelSQL):
|
||||
__name__ = 'test.chat.room'
|
||||
|
||||
|
||||
def register(module):
|
||||
Pool.register(
|
||||
ChatRoom,
|
||||
module=module, type_='model')
|
||||
@@ -28,5 +28,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.message" id="msg_binary_required_sql_constraint">
|
||||
<field name="text">Constraint must be checked.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_chat">
|
||||
<field name="text">Chat Message: %(foo)s</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -0,0 +1,62 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
from .test_tryton import TestCase, activate_module, with_transaction
|
||||
|
||||
|
||||
class NotificationTestCase(TestCase):
|
||||
"Test Notification"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
activate_module('tests')
|
||||
|
||||
def run_tasks(self):
|
||||
pool = Pool()
|
||||
Queue = pool.get('ir.queue')
|
||||
transaction = Transaction()
|
||||
while transaction.tasks:
|
||||
task = Queue(transaction.tasks.pop())
|
||||
task.run()
|
||||
|
||||
@with_transaction()
|
||||
def test_chat_post(self):
|
||||
"Test posting on chat"
|
||||
pool = Pool()
|
||||
User = pool.get('res.user')
|
||||
Room = pool.get('test.chat.room')
|
||||
Notification = pool.get('res.notification')
|
||||
Channel = pool.get('ir.chat.channel')
|
||||
Message = pool.get('ir.chat.message')
|
||||
|
||||
alice, = User.create([{
|
||||
'name': "Alice",
|
||||
'login': 'alice',
|
||||
}])
|
||||
room = Room()
|
||||
room.save()
|
||||
Channel.subscribe(room, alice.login)
|
||||
channel = Channel._get_channel(room)
|
||||
|
||||
room.chat_post('tests.msg_chat', foo="Bar", audience='public')
|
||||
self.run_tasks()
|
||||
|
||||
message, = Message.search([('channel', '=', channel)])
|
||||
self.assertEqual(message.audience, 'public')
|
||||
self.assertEqual(message.content, "Chat Message: Bar")
|
||||
with Transaction().set_user(alice.id):
|
||||
notification, = Notification.get()
|
||||
self.assertEqual(notification, {
|
||||
'id': notification['id'],
|
||||
'label': None,
|
||||
'description': "Chat Message: Bar",
|
||||
'icon': 'tryton-chat',
|
||||
'model': Room.__name__,
|
||||
'records': [room.id],
|
||||
'action': None,
|
||||
'unread': True,
|
||||
})
|
||||
Reference in new issue
Block a user