summaryrefslogtreecommitdiff
path: root/addons/mail/models
diff options
context:
space:
mode:
authorJérome Maes <jem@openerp.com>2015-06-01 16:37:07 +0200
committerAaron Bohy <aab@odoo.com>2015-09-01 20:16:08 +0200
commit4af96310721237d3640bbd162ff9ea84e627028d (patch)
tree2deb74fa477c9ea33b1a0a65199b96e2d6e5f02b /addons/mail/models
parent5907be1f335dc21934619a34fc928a19e7885211 (diff)
[MOV] mail, im_chat : move files and code from im_chat to mail and bus
- im_chat.session will be replaced by mail.channel - im_chat.shortode is renamed into mail.shortcode - im_chat.presence is moved to bus module - js and controller code is moved from im_chat to mail module This commit only move files, and modify manifests, bundles, ... The code will be adapt in the next commits.
Diffstat (limited to 'addons/mail/models')
-rw-r--r--addons/mail/models/__init__.py3
-rw-r--r--addons/mail/models/mail_channel.py138
-rw-r--r--addons/mail/models/mail_shortcode.py48
3 files changed, 188 insertions, 1 deletions
diff --git a/addons/mail/models/__init__.py b/addons/mail/models/__init__.py
index ca72296a6fb..d24e5295cf0 100644
--- a/addons/mail/models/__init__.py
+++ b/addons/mail/models/__init__.py
@@ -9,9 +9,10 @@ import mail_mail
import mail_thread
import mail_channel
import mail_template
+import mail_shortcode
import res_partner
import res_users
import res_config
import update
import ir_actions
-import html2text \ No newline at end of file
+import html2text
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index f118133ddf9..635a4999cce 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -14,6 +14,8 @@ class ChannelPartner(models.Model):
partner_id = fields.Many2one('res.partner', string='Recipient')
channel_id = fields.Many2one('mail.channel', string='Channel')
seen_message_id = fields.Many2one('mail.message', string='Last Seen')
+ state = fields.Selection([('open', 'Open'), ('folded', 'Folded'), ('closed', 'Closed')], string='Status', default='open')
+ seen_datetime = fields.Datetime('Last Seen Datetime')
class Channel(models.Model):
@@ -161,3 +163,139 @@ class Channel(models.Model):
headers['X-Forge-To'] = list_to
res['headers'] = repr(headers)
return res
+
+
+ # --------------------------
+ # Session Methods
+ # --------------------------
+ @api.multi
+ def session_info(self):
+ """ Get the header of the current session
+ :returns the values of the session
+ :rtype : dict
+ """
+ self.ensure_one()
+ info = {
+ 'uuid': self.uuid,
+ 'users': self.session_user_info(),
+ 'state': 'open',
+ }
+ # add user session state, if available and if user is logged
+ if self._uid:
+ domain = [('user_id', '=', self._uid), ('session_id', 'in', self.ids)]
+ session_state = self.env['im_chat.conversation_state'].search(domain, limit=1)
+ if session_state:
+ info['state'] = session_state.state
+ return info
+
+ @api.multi
+ def session_user_info(self):
+ """ Get the user infos for all the identified user in the session
+ :returns a list of user infos
+ :rtype : list(dict)
+ """
+ self.ensure_one()
+ return self.env['res.users'].browse(self.user_ids.ids).read(['id', 'name', 'im_status'])
+
+ @api.model
+ def session_user_image(self, uuid, user_id):
+ """ Get the avatar of a user in the given session
+ :param uuid : the uuid of the session
+ :param user_id : the user identifier
+ """
+ # get the session
+ image_b64 = False
+ if user_id:
+ session = self.env["im_chat.session"].search([('uuid', '=', uuid)], limit=1)
+ if session and session.sudo(user_id).is_in_session():
+ image_b64 = self.env["res.users"].sudo().browse(user_id).image_small
+ # set default image if not found
+ if not image_b64:
+ image_b64 = 'R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
+ return image_b64
+
+ @api.model
+ def session_get(self, user_to):
+ """ Get the canonical session between 2 users, create it if needed.
+ To reuse an old session, this one must be private, and contains only 2 users.
+ :param user_to : the identifier of the user
+ :returns a session header, or False if the user_to was False
+ :rtype : dict
+ """
+ if user_to:
+ session = self.search([('user_ids', 'in', user_to), ('user_ids', 'in', [self._uid])], limit=1)
+ if not (session and len(session.user_ids) == 2 and session.is_private()):
+ session = self.create({'user_ids': [(6, 0, (user_to, self._uid))]})
+ return session.session_info()
+
+ # --------------------------
+ # Utils Methods
+ # --------------------------
+ @api.multi
+ def is_private(self):
+ """ Return true if the session is private between identified users (no external messages).
+ The only way to do that is to check if there is a message without author (from_id is False).
+ """
+ self.ensure_one()
+ message_ids = self.env["im_chat.message"].search([('to_id', 'in', self.ids), ('from_id', '=', None)])
+ return len(message_ids) == 0
+
+ @api.multi
+ def is_in_session(self):
+ """ Return True if the current user is in the user_ids of the session. False otherwise.
+ Note : the user need to be logged
+ """
+ self.ensure_one()
+ user_id = self._uid
+ return user_id and user_id in [u.id for u in self.user_ids]
+
+ @api.model
+ def update_state(self, uuid, state=None):
+ """ Update the fold_state of the given session. In order to syncronize web browser
+ tabs, the change will be broadcast to himself (the current user channel).
+ Note : the user need to be logged
+ :param status : the new status of the session for the current user.
+ """
+ domain = [('user_id', '=', self._uid), ('session_id.uuid', '=', uuid)]
+ for session_state in self.env['im_chat.conversation_state'].search(domain):
+ if not state:
+ state = session_state.state
+ if session_state.state == 'open':
+ state = 'folded'
+ else:
+ state = 'open'
+ session_state.write({'state': state})
+ self.env['bus.bus'].sendone((self._cr.dbname, 'im_chat.session', self._uid), session_state.session_id.session_info())
+
+ @api.model
+ def get_init_notifications(self):
+ """ Get unread messages and old messages received less than AWAY_TIMER
+ ago and the session_info for open or folded window
+ Note : the user need to be logged
+ """
+ # get the message since the AWAY_TIMER
+ threshold = datetime.datetime.now() - datetime.timedelta(seconds=AWAY_TIMER)
+ threshold = threshold.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+ domain = [('to_id.user_ids', 'in', [self._uid]), ('create_date', '>', threshold)]
+
+ # get the message since the last poll of the user
+ presence = self.env['im_chat.presence'].search([('user_id', '=', self._uid)], limit=1)
+ if presence:
+ domain.append(('create_date', '>', presence.last_poll))
+ messages = self.search_read(domain, ['from_id', 'to_id', 'create_date', 'type', 'message'], order='id asc')
+
+ # get the session of the messages and the not-closed ones
+ session_ids = [m['to_id'][0] for m in messages]
+ domain = [('user_id', '=', self._uid), '|', ('state', '!=', 'closed'), ('session_id', 'in', session_ids)]
+ session_states = self.env['im_chat.conversation_state'].search(domain)
+ # re-open the session where a message have been recieved recently
+ session_states.filtered(lambda r: r.state == 'closed').write({'state': 'folded'})
+
+ # create the notifications (session infos first, then messages)
+ notifications = []
+ for state in session_states:
+ session_infos = state.session_id.session_info()
+ notifications.append([(self._cr.dbname, 'im_chat.session', self._uid), session_infos])
+ for message in messages:
+ notifications.append([(self._cr.dbname, 'im_chat.session', self._uid), message])
+ return notifications
diff --git a/addons/mail/models/mail_shortcode.py b/addons/mail/models/mail_shortcode.py
new file mode 100644
index 00000000000..42627e5abe5
--- /dev/null
+++ b/addons/mail/models/mail_shortcode.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+import cgi
+import re
+
+from openerp import api, fields, models
+
+
+class ImChatShortcode(models.Model):
+ """ Shortcode
+ Canned Responses, allowing the user to defined shortcuts in its chat message.
+ These shortcode are globals and are available for every user. Smiley use this mecanism.
+ """
+
+ _name = 'im_chat.shortcode'
+ _description = 'Canned Response / Shortcode'
+
+ source = fields.Char('Shortcut', required=True, index=True, help="The shortcut which must be replace in the Chat Messages")
+ substitution = fields.Char('Substitution', required=True, index=True, help="The excaped html code replacing the shortcut")
+ description = fields.Char('Description')
+
+ @api.model
+ def create(self, values):
+ if values.get('substitution'):
+ values['substitution'] = self._sanitize_shorcode(values['substitution'])
+ return super(ImChatShortcode, self).create(values)
+
+ @api.multi
+ def write(self, values):
+ if values.get('substitution'):
+ values['substitution'] = self._sanitize_shorcode(values['substitution'])
+ return super(ImChatShortcode, self).write(values)
+
+ def _sanitize_shorcode(self, substitution):
+ """ Sanitize the shortcode substitution :
+ - HTML substitution : only allow the img tag (smiley)
+ - escape other substitutions to avoid XSS
+ """
+ is_img_tag = re.match(r'''^<img\s+src=('|")([^'"]*)\1\s*/?>$''', substitution, re.M|re.I)
+ if is_img_tag:
+ return substitution
+ return cgi.escape(substitution)
+
+ @api.model
+ def replace_shortcode(self, message):
+ for shortcode in self.search([]):
+ regex = '(?:^|\s)(%s)(?:\s|$)' % re.escape(shortcode.source)
+ message = re.sub(regex, " " + shortcode.substitution + " ", message)
+ return message