when you implement ERP for whole company elements, they always need your support to use it, the simple solution for that is online web chat.
some people asking me about where i download my instant messaging for open ERP, but really i don't know where, because it was long time ago.
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL ().
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
#
##############################################################################
import openerp
import openerp.tools.config
import openerp.modules.registry
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
import datetime
from openerp.osv import osv, fields
import time
import logging
import json
import select
_logger = logging.getLogger(__name__)
def listen_channel(cr, channel_name, handle_message, check_stop=(lambda: False), check_stop_timer=60.):
"""
Begin a loop, listening on a PostgreSQL channel. This method does never terminate by default, you need to provide a check_stop
callback to do so. This method also assume that all notifications will include a message formated using JSON (see the
corresponding notify_channel() method).
:param db_name: database name
:param channel_name: the name of the PostgreSQL channel to listen
:param handle_message: function that will be called when a message is received. It takes one argument, the message
attached to the notification.
:type handle_message: function (one argument)
:param check_stop: function that will be called periodically (see the check_stop_timer argument). If it returns True
this function will stop to watch the channel.
:type check_stop: function (no arguments)
:param check_stop_timer: The maximum amount of time between calls to check_stop_timer (can be shorter if messages
are received).
"""
try:
conn = cr._cnx
cr.execute("listen " + channel_name + ";")
cr.commit();
stopping = False
while not stopping:
if check_stop():
stopping = True
break
if select.select([conn], [], [], check_stop_timer) == ([],[],[]):
pass
else:
conn.poll()
while conn.notifies:
message = json.loads(conn.notifies.pop().payload)
handle_message(message)
finally:
try:
cr.execute("unlisten " + channel_name + ";")
cr.commit()
except:
pass # can't do anything if that fails
def notify_channel(cr, channel_name, message):
"""
Send a message through a PostgreSQL channel. The message will be formatted using JSON. This method will
commit the given transaction because the notify command in Postgresql seems to work correctly when executed in
a separate transaction (despite what is written in the documentation).
:param cr: The cursor.
:param channel_name: The name of the PostgreSQL channel.
:param message: The message, must be JSON-compatible data.
"""
cr.commit()
cr.execute("notify " + channel_name + ", %s", [json.dumps(message)])
cr.commit()
POLL_TIMER = 30
DISCONNECTION_TIMER = POLL_TIMER + 5
WATCHER_ERROR_DELAY = 10
if openerp.evented:
import gevent
import gevent.event
class ImWatcher(object):
watchers = {}
@staticmethod
def get_watcher(db_name):
if not ImWatcher.watchers.get(db_name):
ImWatcher(db_name)
return ImWatcher.watchers[db_name]
def __init__(self, db_name):
self.db_name = db_name
ImWatcher.watchers[db_name] = self
self.waiting = 0
self.wait_id = 0
self.users = {}
self.users_watch = {}
gevent.spawn(self.loop)
def loop(self):
_logger.info("Begin watching on channel im_channel for database " + self.db_name)
stop = False
while not stop:
try:
registry = openerp.modules.registry.RegistryManager.get(self.db_name)
with registry.cursor() as cr:
listen_channel(cr, "im_channel", self.handle_message, self.check_stop)
stop = True
except:
# if something crash, we wait some time then try again
_logger.exception("Exception during watcher activity")
time.sleep(WATCHER_ERROR_DELAY)
_logger.info("End watching on channel im_channel for database " + self.db_name)
del ImWatcher.watchers[self.db_name]
def handle_message(self, message):
if message["type"] == "message":
for waiter in self.users.get(message["receiver"], {}).values():
waiter.set()
else: #type status
for waiter in self.users_watch.get(message["user"], {}).values():
waiter.set()
def check_stop(self):
return self.waiting == 0
def _get_wait_id(self):
self.wait_id += 1
return self.wait_id
def stop(self, user_id, watch_users, timeout=None):
wait_id = self._get_wait_id()
event = gevent.event.Event()
self.waiting += 1
self.users.setdefault(user_id, {})[wait_id] = event
for watch in watch_users:
self.users_watch.setdefault(watch, {})[wait_id] = event
try:
event.wait(timeout)
finally:
for watch in watch_users:
del self.users_watch[watch][wait_id]
if len(self.users_watch[watch]) == 0:
del self.users_watch[watch]
del self.users[user_id][wait_id]
if len(self.users[user_id]) == 0:
del self.users[user_id]
self.waiting -= 1
class LongPollingController(openerp.addons.web.http.Controller):
_cp_path = '/longpolling/im'
@openerp.addons.web.http.jsonrequest
def poll(self, req, last=None, users_watch=None, db=None, uid=None, password=None, uuid=None):
assert_uuid(uuid)
if not openerp.evented:
raise Exception("Not usable in a server not running gevent")
if db is not None:
req.session._db = db
req.session._uid = uid
req.session._password = password
req.session.model('im.user').im_connect(uuid=uuid, context=req.context)
my_id = req.session.model('im.user').get_by_user_id(uuid or req.session._uid, req.context)["id"]
num = 0
while True:
res = req.session.model('im.message').get_messages(last, users_watch, uuid=uuid, context=req.context)
if num >= 1 or len(res["res"]) > 0:
return res
last = res["last"]
num += 1
ImWatcher.get_watcher(res["dbname"]).stop(my_id, users_watch or [], POLL_TIMER)
@openerp.addons.web.http.jsonrequest
def activated(self, req):
return not not openerp.evented
@openerp.addons.web.http.jsonrequest
def gen_uuid(self, req):
import uuid
return "%s" % uuid.uuid1()
def assert_uuid(uuid):
if not isinstance(uuid, (str, unicode, type(None))):
raise Exception("%s is not a uuid" % uuid)
class im_message(osv.osv):
_name = 'im.message'
_order = "date desc"
_columns = {
'message': fields.char(string="Message", size=200, required=True),
'from_id': fields.many2one("im.user", "From", required= True, ondelete='cascade'),
'to_id': fields.many2one("im.user", "To", required=True, select=True, ondelete='cascade'),
'date': fields.datetime("Date", required=True, select=True),
}
_defaults = {
'date': lambda *args: datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
}
def get_messages(self, cr, uid, last=None, users_watch=None, uuid=None, context=None):
assert_uuid(uuid)
users_watch = users_watch or []
# complex stuff to determine the last message to show
users = self.pool.get("im.user")
my_id = users.get_by_user_id(cr, uid, uuid or uid, context=context)["id"]
c_user = users.browse(cr, openerp.SUPERUSER_ID, my_id, context=context)
if last:
if c_user.im_last_received < last:
users.write(cr, openerp.SUPERUSER_ID, my_id, {'im_last_received': last}, context=context)
else:
last = c_user.im_last_received or -1
# how fun it is to always need to reorder results from read
mess_ids = self.search(cr, openerp.SUPERUSER_ID, [['id', '>', last], ['to_id', '=', my_id]], order="id", context=context)
mess = self.read(cr, openerp.SUPERUSER_ID, mess_ids, ["id", "message", "from_id", "date"], context=context)
index = {}
for i in xrange(len(mess)):
index[mess[i]["id"]] = mess[i]
mess = []
for i in mess_ids:
mess.append(index[i])
if len(mess) > 0:
last = mess[-1]["id"]
users_status = users.read(cr, openerp.SUPERUSER_ID, users_watch, ["im_status"], context=context)
return {"res": mess, "last": last, "dbname": cr.dbname, "users_status": users_status}
def post(self, cr, uid, message, to_user_id, uuid=None, context=None):
assert_uuid(uuid)
my_id = self.pool.get('im.user').get_by_user_id(cr, uid, uuid or uid)["id"]
self.create(cr, openerp.SUPERUSER_ID, {"message": message, 'from_id': my_id, 'to_id': to_user_id}, context=context)
notify_channel(cr, "im_channel", {'type': 'message', 'receiver': to_user_id})
return False
class im_user(osv.osv):
_name = "im.user"
def _im_status(self, cr, uid, ids, something, something_else, context=None):
res = {}
current = datetime.datetime.now()
delta = datetime.timedelta(0, DISCONNECTION_TIMER)
data = self.read(cr, openerp.SUPERUSER_ID, ids, ["im_last_status_update", "im_last_status"], context=context)
for obj in data:
last_update = datetime.datetime.strptime(obj["im_last_status_update"], DEFAULT_SERVER_DATETIME_FORMAT)
res[obj["id"]] = obj["im_last_status"] and (last_update + delta) > current
return res
def search_users(self, cr, uid, domain, fields, limit, context=None):
# do not user openerp.SUPERUSER_ID, reserved to normal users
found = self.pool.get('res.users').search(cr, uid, domain, limit=limit, context=context)
found = self.get_by_user_ids(cr, uid, found, context=context)
return self.read(cr, uid, found, fields, context=context)
def im_connect(self, cr, uid, uuid=None, context=None):
assert_uuid(uuid)
return self._im_change_status(cr, uid, True, uuid, context)
def im_disconnect(self, cr, uid, uuid=None, context=None):
assert_uuid(uuid)
return self._im_change_status(cr, uid, False, uuid, context)
def _im_change_status(self, cr, uid, new_one, uuid=None, context=None):
assert_uuid(uuid)
id = self.get_by_user_id(cr, uid, uuid or uid, context=context)["id"]
current_status = self.read(cr, openerp.SUPERUSER_ID, id, ["im_status"], context=None)["im_status"]
self.write(cr, openerp.SUPERUSER_ID, id, {"im_last_status": new_one,
"im_last_status_update": datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
if current_status != new_one:
notify_channel(cr, "im_channel", {'type': 'status', 'user': id})
return True
def get_by_user_id(self, cr, uid, id, context=None):
ids = self.get_by_user_ids(cr, uid, [id], context=context)
return ids[0]
def get_by_user_ids(self, cr, uid, ids, context=None):
user_ids = [x for x in ids if isinstance(x, int)]
uuids = [x for x in ids if isinstance(x, (str, unicode))]
users = self.search(cr, openerp.SUPERUSER_ID, ["|", ["user", "in", user_ids], ["uuid", "in", uuids]], context=None)
records = self.read(cr, openerp.SUPERUSER_ID, users, ["user", "uuid"], context=None)
inside = {}
for i in records:
if i["user"]:
inside[i["user"][0]] = True
elif ["uuid"]:
inside[i["uuid"]] = True
not_inside = {}
for i in ids:
if not (i in inside):
not_inside[i] = True
for to_create in not_inside.keys():
if isinstance(to_create, int):
created = self.create(cr, openerp.SUPERUSER_ID, {"user": to_create}, context=context)
records.append({"id": created, "user": [to_create, ""]})
else:
created = self.create(cr, openerp.SUPERUSER_ID, {"uuid": to_create}, context=context)
records.append({"id": created, "uuid": to_create})
return records
def assign_name(self, cr, uid, uuid, name, context=None):
assert_uuid(uuid)
id = self.get_by_user_id(cr, uid, uuid or uid, context=context)["id"]
self.write(cr, openerp.SUPERUSER_ID, id, {"assigned_name": name}, context=context)
return True
def _get_name(self, cr, uid, ids, name, arg, context=None):
res = {}
for record in self.browse(cr, uid, ids, context=context):
res[record.id] = record.assigned_name
if record.user:
res[record.id] = record.user.name
continue
return res
_columns = {
'name': fields.function(_get_name, type='char', size=200, string="Name", store=True, readonly=True),
'assigned_name': fields.char(string="Assigned Name", size=200, required=False),
'image': fields.related('user', 'image_small', type='binary', string="Image", readonly=True),
'user': fields.many2one("res.users", string="User", select=True, ondelete='cascade'),
'uuid': fields.char(string="UUID", size=50, select=True),
'im_last_received': fields.integer(string="Instant Messaging Last Received Message"),
'im_last_status': fields.boolean(strint="Instant Messaging Last Status"),
'im_last_status_update': fields.datetime(string="Instant Messaging Last Status Update"),
'im_status': fields.function(_im_status, string="Instant Messaging Status", type='boolean'),
}
_defaults = {
'im_last_received': -1,
'im_last_status': False,
'im_last_status_update': lambda *args: datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
}