diff options
| author | Jérome Maes <jem@openerp.com> | 2015-06-01 16:37:07 +0200 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2015-09-01 20:16:08 +0200 |
| commit | 4af96310721237d3640bbd162ff9ea84e627028d (patch) | |
| tree | 2deb74fa477c9ea33b1a0a65199b96e2d6e5f02b /addons/mail/models | |
| parent | 5907be1f335dc21934619a34fc928a19e7885211 (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__.py | 3 | ||||
| -rw-r--r-- | addons/mail/models/mail_channel.py | 138 | ||||
| -rw-r--r-- | addons/mail/models/mail_shortcode.py | 48 |
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 |
