From e6f038a8216c249a3ce7d9f490010d9f666f3375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 4 Aug 2015 12:38:44 +0200 Subject: [PATCH] [REF] mail: mail_thread: followers update Followers can now be partners or channels. Partners following a document will receive needaction, as previously. However people can follow documents through channels. Members of a channel are able to listen to a stream of messages using the channel. Those messages do not create needaction messages. It is therefore possible to follow documents without receiving too much notifications. For interesting documents subscribing with its partner will create notification. message_follower_ids fields is udpated. It is now a many2many to mail.followers, not to res.partner anymore. A subscription can be either a partner (partner_id) or a channel (channel_id). Some access rules have been updated accordingly. --- addons/base_action_rule/test_models.py | 2 +- addons/calendar/calendar.py | 14 +- addons/hr_evaluation/hr_evaluation.py | 5 +- addons/mail/controllers/main.py | 8 +- addons/mail/data/mail_channel_demo.xml | 9 - addons/mail/models/mail_followers.py | 83 ++++++++- addons/mail/models/mail_thread.py | 174 +++++++----------- addons/mail/models/res_users.py | 2 +- addons/mail/security/ir.model.access.csv | 4 +- addons/mail/security/mail_security.xml | 5 +- addons/mail/static/src/js/mail_followers.js | 68 ++++--- addons/mail/static/src/xml/mail_followers.xml | 11 +- addons/mail/tests/test_invite.py | 5 +- addons/mail/tests/test_mail_channel.py | 2 +- addons/mail/tests/test_mail_features.py | 25 ++- addons/mail/tests/test_mail_followers.py | 77 +++++--- addons/mail/tests/test_mail_gateway.py | 6 +- addons/mail/wizard/invite.py | 8 +- addons/mail/wizard/invite_view.xml | 2 + addons/note/note_demo.xml | 4 - addons/note/security/ir.rule.xml | 2 +- addons/portal/tests/test_portal.py | 2 +- addons/portal_sale/mail_mail.py | 4 +- addons/portal_sale/portal_sale.py | 4 +- .../portal_sale/security/portal_security.xml | 10 +- .../portal_stock/security/portal_security.xml | 4 +- .../product_email_template/models/invoice.py | 2 +- addons/project/project_data.xml | 1 - addons/project/project_demo.xml | 10 +- addons/project/security/project_security.xml | 16 +- addons/project/tests/test_access_rights.py | 4 +- addons/project/tests/test_project_flow.py | 2 +- .../security/project_issue_security.xml | 8 +- .../security/portal_security.xml | 4 +- .../security/project_timesheet_security.xml | 4 +- .../tests/test_website_blog_flow.py | 8 +- .../security/portal_security.xml | 2 +- addons/website_forum/controllers/main.py | 2 +- addons/website_mail/controllers/main.py | 1 + addons/website_portal_sale/data/demo.xml | 4 +- .../website_project_issue/controllers/main.py | 5 +- .../security/portal_security.xml | 6 +- .../security/portal_security.xml | 4 +- addons/website_sale/controllers/main.py | 2 +- 44 files changed, 344 insertions(+), 281 deletions(-) diff --git a/addons/base_action_rule/test_models.py b/addons/base_action_rule/test_models.py index fde48b815ad..5eb0dc1ff8b 100644 --- a/addons/base_action_rule/test_models.py +++ b/addons/base_action_rule/test_models.py @@ -30,5 +30,5 @@ class lead_test(osv.Model): def message_post(self, cr, uid, thread_id, body='', subject=None, message_type='notification', subtype=None, parent_id=False, attachments=None, context=None, **kwargs): pass - def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None): + def message_subscribe(self, cr, uid, ids, partner_ids=None, channel_ids=None, subtype_ids=None, force=True, context=None): pass diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index acd1a19f16f..aa89eb6cc90 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -1395,11 +1395,17 @@ class calendar_event(osv.Model): del context['default_date'] return super(calendar_event, self).message_post(cr, uid, thread_id, context=context, **kwargs) - def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None): - return super(calendar_event, self).message_subscribe(cr, uid, get_real_ids(ids), partner_ids, subtype_ids=subtype_ids, context=context) + def message_subscribe(self, cr, uid, ids, partner_ids=None, channel_ids=None, subtype_ids=None, force=True, context=None): + return super(calendar_event, self).message_subscribe( + cr, uid, get_real_ids(ids), + partner_ids=partner_ids, + channel_ids=channel_ids, + subtype_ids=subtype_ids, + force=force, + context=context) - def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None): - return super(calendar_event, self).message_unsubscribe(cr, uid, get_real_ids(ids), partner_ids, context=context) + def message_unsubscribe(self, cr, uid, ids, partner_ids=None, channel_ids=None, context=None): + return super(calendar_event, self).message_unsubscribe(cr, uid, get_real_ids(ids), partner_ids=partner_ids, channel_ids=channel_ids, context=context) def do_sendmail(self, cr, uid, ids, context=None): for event in self.browse(cr, uid, ids, context): diff --git a/addons/hr_evaluation/hr_evaluation.py b/addons/hr_evaluation/hr_evaluation.py index e9998279379..e7d181f1e2a 100644 --- a/addons/hr_evaluation/hr_evaluation.py +++ b/addons/hr_evaluation/hr_evaluation.py @@ -240,8 +240,11 @@ class hr_evaluation(osv.Model): def write(self, cr, uid, ids, vals, context=None): if vals.get('employee_id'): employee_id = self.pool.get('hr.employee').browse(cr, uid, vals.get('employee_id'), context=context) + # TDE FIXME: use add_command if employee_id.parent_id and employee_id.parent_id.user_id: - vals['message_follower_ids'] = [(4, employee_id.parent_id.user_id.partner_id.id)] + message_follower_ids = vals.get('message_follower_ids') or [] # webclient can send None or False + message_follower_ids += self.env['mail.followers']._add_follower_command(self._name, ids, {employee_id.parent_id.user_id.partner_id.id: None}, {}, force=True)[0] + vals['message_follower_ids'] = message_follower_ids if 'date' in vals: new_vals = {'deadline': vals.get('date')} obj_hr_eval_iterview = self.pool.get('hr.evaluation.interview') diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py index 8b72fc74106..a9c6d98eaac 100644 --- a/addons/mail/controllers/main.py +++ b/addons/mail/controllers/main.py @@ -48,12 +48,14 @@ class MailController(http.Controller): def read_followers(self, follower_ids): result = [] is_editable = request.env.user.has_group('base.group_no_one') - for follower in request.env['res.partner'].browse(follower_ids): + for follower in request.env['mail.followers'].browse(follower_ids): result.append({ 'id': follower.id, - 'name': follower.name, + 'name': follower.partner_id.name or follower.channel_id.name, + 'res_model': 'res.partner' if follower.partner_id else 'mail.channel', + 'res_id': follower.partner_id.id or follower.channel_id.id, 'is_editable': is_editable, - 'is_uid': request.env.user.partner_id == follower, + 'is_uid': request.env.user.partner_id == follower.partner_id, }) return result diff --git a/addons/mail/data/mail_channel_demo.xml b/addons/mail/data/mail_channel_demo.xml index d14f65acd9a..3a6fd6bc6ad 100644 --- a/addons/mail/data/mail_channel_demo.xml +++ b/addons/mail/data/mail_channel_demo.xml @@ -7,23 +7,14 @@ Best Sales Practices Discussion about best sales practices and deals. - - - Board meetings Board meetings, budgets, strategic plans - - - R&D Research and development discussion group - - - diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py index 59d6d5d4254..2e71ff1433c 100644 --- a/addons/mail/models/mail_followers.py +++ b/addons/mail/models/mail_followers.py @@ -23,11 +23,87 @@ class Followers(models.Model): res_id = fields.Integer( 'Related Document ID', select=1, help='Id of the followed resource') partner_id = fields.Many2one( - 'res.partner', string='Related Partner', ondelete='cascade', required=True, select=1) + 'res.partner', string='Related Partner', ondelete='cascade', select=1) + channel_id = fields.Many2one( + 'mail.channel', string='Listener', ondelete='cascade', select=1) subtype_ids = fields.Many2many( 'mail.message.subtype', string='Subtype', help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall.") + @api.model + def _add_follower_command(self, res_model, res_ids, partner_data, channel_data, force=True): + """ Please upate me + :param force: if True, delete existing followers before creating new one + using the subtypes given in the parameters + """ + force_mode = force or (all(data for data in partner_data.values()) and all(data for data in channel_data.values())) + generic = [] + specific = {} + existing = {} # {res_id: follower_ids} + p_exist = {} # {partner_id: res_ids} + c_exist = {} # {channel_id: res_ids} + + followers = self.sudo().search([ + '&', + '&', ('res_model', '=', res_model), ('res_id', 'in', res_ids), + '|', ('partner_id', 'in', partner_data.keys()), ('channel_id', 'in', channel_data.keys())]) + + if force_mode: + followers.unlink() + else: + for follower in followers: + existing.setdefault(follower.res_id, list()).append(follower) + if follower.partner_id: + p_exist.setdefault(follower.partner_id.id, list()).append(follower.res_id) + if follower.channel_id: + c_exist.setdefault(follower.channel_id.id, list()).append(follower.res_id) + + default_subtypes = self.env['mail.message.subtype'].search([ + ('default', '=', True), + '|', ('res_model', '=', res_model), ('res_model', '=', False)]) + + if force_mode: + for pid, data in partner_data.iteritems(): + if not data: + partner_data[pid] = default_subtypes.ids + for cid, data in channel_data.iteritems(): + if not data: + channel_data[cid] = default_subtypes.ids + + # create new followers, batch ok + gen_new_pids = [pid for pid in partner_data.keys() if pid not in p_exist] + gen_new_cids = [cid for cid in channel_data.keys() if cid not in c_exist] + for pid in gen_new_pids: + generic.append([0, 0, {'res_model': res_model, 'partner_id': pid, 'subtype_ids': [(6, 0, partner_data.get(pid) or default_subtypes.ids)]}]) + for cid in gen_new_cids: + generic.append([0, 0, {'res_model': res_model, 'channel_id': cid, 'subtype_ids': [(6, 0, channel_data.get(cid) or default_subtypes.ids)]}]) + + # create new followers, each document at a time because of existing followers to avoid erasing + if not force_mode: + for res_id in res_ids: + command = [] + doc_followers = existing.get(res_id, list()) + + new_pids = set(partner_data.keys()) - set([sub.partner_id.id for sub in doc_followers if sub.partner_id]) - set(gen_new_pids) + new_cids = set(channel_data.keys()) - set([sub.channel_id.id for sub in doc_followers if sub.channel_id]) - set(gen_new_cids) + + # subscribe new followers + for new_pid in new_pids: + command.append((0, 0, { + 'res_model': res_model, + 'partner_id': new_pid, + 'subtype_ids': [(6, 0, partner_data.get(new_pid) or default_subtypes.ids)], + })) + for new_cid in new_cids: + command.append((0, 0, { + 'res_model': res_model, + 'channel_id': new_cid, + 'subtype_ids': [(6, 0, channel_data.get(new_cid) or default_subtypes.ids)], + })) + if command: + specific[res_id] = command + return generic, specific + # # Modifying followers change access rights to individual documents. As the # cache may contain accessible/inaccessible data, one has to refresh it. @@ -50,7 +126,10 @@ class Followers(models.Model): self.invalidate_cache() return res - _sql_constraints = [('mail_followers_res_partner_res_model_id_uniq', 'unique(res_model,res_id,partner_id)', 'Error, a partner cannot follow twice the same object.')] + _sql_constraints = [ + ('mail_followers_res_partner_res_model_id_uniq', 'unique(res_model,res_id,partner_id)', 'Error, a partner cannot follow twice the same object.'), + ('mail_followers_res_channel_res_model_id_uniq', 'unique(res_model,res_id,channel_id)', 'Error, a channel cannot follow twice the same object.'), + ] class Notification(models.Model): diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 661431f6fc5..ecca6887b38 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -66,11 +66,16 @@ class MailThread(models.AbstractModel): _mail_post_access = 'write' # access required on the document to post on it _mail_mass_mailing = False # enable mass mailing on this model - message_is_follower = fields.Boolean('Is Follower', compute='_get_followers', search='_search_is_follower') - message_follower_ids = fields.Many2many( - comodel_name='res.partner', string='Followers', - inverse='_set_followers', - compute='_get_followers', search='_search_followers', type='many2many') + message_is_follower = fields.Boolean('Is Follower', compute='_compute_is_follower') + message_follower_ids = fields.One2many( + 'mail.followers', 'res_id', string='Followers', + domain=lambda self: [('res_model', '=', self._name)]) + message_partner_ids = fields.Many2many( + comodel_name='res.partner', string='Followers (Partners)', + compute='_get_followers', search='_search_follower_partners') + message_channel_ids = fields.Many2many( + comodel_name='mail.channel', string='Followers (Channels)', + compute='_get_followers', search='_search_follower_channels') message_ids = fields.One2many( 'mail.message', 'res_id', string='Messages', domain=lambda self: [('model', '=', self._name)], auto_join=True, @@ -83,72 +88,45 @@ class MailThread(models.AbstractModel): 'Unread Messages', compute='_get_message_unread', help="Number of unread messages") - @api.multi + @api.one + @api.depends('message_follower_ids') def _get_followers(self): - res = dict.fromkeys(self.ids, self.env['res.partner']) - followers = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('res_id', 'in', self.ids)]) - for follower in followers: - res[follower.res_id] |= follower.partner_id - for record in self: - if not record.id: - continue - record.message_follower_ids = res[record.id] - record.message_is_follower = self.env.user.partner_id in record.message_follower_ids - - @api.multi - def _set_followers(self): - # read the old set of followers, and determine the new set of followers - old = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('res_id', 'in', self.ids)]).mapped('partner_id') - new = self.env['res.partner'] - Partner = self.env['res.partner'] - - old_style_commands = self._fields['message_follower_ids'].convert_to_write(self.message_follower_ids) - for command in old_style_commands: - if command[0] == 0: - new |= Partner.create(command[2]) - elif command[0] == 1: - partner = Partner.browse(command[1]) - partner.write(command[2]) - new |= partner - elif command[0] == 2: - partner = Partner.browse(command[1]) - new -= partner - partner.unlink() - elif command[0] == 3: - new -= Partner.browse(command[1]) - elif command[0] == 4: - new |= Partner.browse(command[1]) - elif command[0] == 5: - new = self.env['res.partner'] - elif command[0] == 6: - new = self.env['res.partner'].browse(command[2]) - - # remove partners that are no longer followers - self.message_unsubscribe((old-new).ids) - # add new followers - self.message_subscribe((new-old).ids) + self.message_partner_ids = self.message_follower_ids.mapped('partner_id') + self.message_channel_ids = self.message_follower_ids.mapped('channel_id') @api.model - def _search_followers(self, operator, operand): + def _search_follower_partners(self, operator, operand): """Search function for message_follower_ids Do not use with operator 'not in'. Use instead message_is_followers """ - # TOFIX make it work with not in - assert operator != "not in", "Do not search message_follower_ids with 'not in'" - followers = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('partner_id', operator, operand)]) + followers = self.env['mail.followers'].sudo().search([ + ('res_model', '=', self._name), + ('partner_id', operator, operand)]) return [('id', 'in', followers.mapped('res_id'))] @api.model - def _search_is_follower(self, operator, operand): - """Search function for message_is_follower""" - user_pid = self.env.user.partner_id.id - if (operator == '=' and operand) or (operator == '!=' and not operand): # is a follower - followers = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('partner_id', '=', user_pid)]) - return [('id', 'in', followers.mapped('res_id'))] - else: # is not a follower or unknown domain - followers = self.env['mail.followers'].sudo().search([('res_model', '=', self._name), ('partner_id', '=', user_pid)]) - return [('id', 'not in', followers.mapped('res_id'))] + def _search_follower_channels(self, operator, operand): + """Search function for message_follower_ids + + Do not use with operator 'not in'. Use instead message_is_followers + """ + followers = self.env['mail.followers'].sudo().search([ + ('res_model', '=', self._name), + ('channel_id', operator, operand)]) + return [('id', 'in', followers.mapped('res_id'))] + + @api.multi + @api.depends('message_follower_ids') + def _compute_is_follower(self): + followers = self.env['mail.followers'].sudo().search([ + ('res_model', '=', self._name), + ('res_id', 'in', self.ids), + ('partner_id', '=', self.env.user.partner_id.id), + ]) + following_ids = followers.mapped('res_id') + for record in self: + record.message_is_follower = record.id in following_ids @api.multi def _get_message_unread(self): @@ -188,7 +166,7 @@ class MailThread(models.AbstractModel): # subscribe uid unless asked not to if not self._context.get('mail_create_nosubscribe'): message_follower_ids = values.get('message_follower_ids') or [] # webclient can send None or False - message_follower_ids.append([4, self.env.user.partner_id.id]) + message_follower_ids += self.env['mail.followers']._add_follower_command(self._name, [], {self.env.user.partner_id.id: None}, {}, force=True)[0] values['message_follower_ids'] = message_follower_ids thread = super(MailThread, self).create(values) @@ -765,7 +743,7 @@ class MailThread(models.AbstractModel): obj = record_set[0] else: obj = self.env[alias.alias_parent_model_id.model].browse(alias.alias_parent_thread_id) - if not author_id or author_id not in [fol.id for fol in obj.message_follower_ids]: + if not author_id or author_id not in [fol.id for fol in obj.message_partner_ids]: _warn('alias %s restricted to internal followers, skipping' % alias.alias_name) _create_bounce_email() return False @@ -1296,7 +1274,7 @@ class MailThread(models.AbstractModel): partner = self.env['res.partner'].sudo().browse([partner_info['partner_id']])[0] if email and email in [val[1] for val in result[self.ids[0]]]: # already existing email -> skip return result - if partner and partner in self.message_follower_ids: # recipient already in the followers -> skip + if partner and partner in self.message_partner_ids: # recipient already in the followers -> skip return result if partner and partner.id in [val[0] for val in result[self.ids[0]]]: # already existing partner ID -> skip return result @@ -1339,8 +1317,8 @@ class MailThread(models.AbstractModel): followers = self.env['res.partner'] if res_model and res_id: record = self.env[res_model].browse(res_id) - if hasattr(record, 'message_follower_ids'): - followers = record.message_follower_ids + if hasattr(record, 'message_partner_ids'): + followers = record.message_partner_ids Partner = self.env['res.partner'].sudo() partner_ids = [] @@ -1601,16 +1579,18 @@ class MailThread(models.AbstractModel): return result @api.multi - def message_subscribe(self, partner_ids, subtype_ids=None): + def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True): """ Add partners to the records followers. """ # not necessary for computation, but saves an access right check - if not partner_ids: + if not partner_ids and not channel_ids: return True + if partner_ids is None: + partner_ids = [] + if channel_ids is None: + channel_ids = [] - Followers = self.env['mail.followers'] - Subtype = self.env['mail.message.subtype'] - - if set(partner_ids) == set([self.env.user.partner_id.id]): + # TDE CHECK THIS + if not channel_ids and partner_ids and set(partner_ids) == set([self.env.user.partner_id.id]): try: self.check_access_rights('read') self.check_access_rule('read') @@ -1620,38 +1600,14 @@ class MailThread(models.AbstractModel): self.check_access_rights('write') self.check_access_rule('write') - existing_pids_dict = dict.fromkeys(self.ids, self.env['res.partner']) - followers = Followers.sudo().search([ - '&', '&', - ('res_model', '=', self._name), - ('res_id', 'in', self.ids), - ('partner_id', 'in', partner_ids)]) - for follower in followers: - existing_pids_dict[follower.res_id] |= follower.partner_id - - # subtype_ids specified: update already subscribed partners - if subtype_ids and followers: - followers.write({'subtype_ids': [(6, 0, subtype_ids)]}) - # subtype_ids not specified: do not update already subscribed partner, fetch default subtypes for new partners - if subtype_ids is None: - subtype_ids = Subtype.search([ - ('default', '=', True), - '|', - ('res_model', '=', self._name), - ('res_model', '=', False)]).ids - - for record in self: - existing_pids = existing_pids_dict[record.id] - new_partners = self.env['res.partner'].browse(partner_ids) - existing_pids - - # subscribe new followers - for new_partner in new_partners: - Followers.sudo().create({ - 'res_model': self._name, - 'res_id': record.id, - 'partner_id': new_partner.id, - 'subtype_ids': [(6, 0, subtype_ids)]}) + partner_data = dict((pid, subtype_ids) for pid in partner_ids) + channel_data = dict((cid, subtype_ids) for cid in channel_ids) + gen, part = self.env['mail.followers']._add_follower_command(self._name, self.ids, partner_data, channel_data, force=force) + self.sudo().write({'message_follower_ids': gen}) + for record in self.filtered(lambda self: self.id in part): + record.write(part[record.id]) + self.invalidate_cache() return True @api.multi @@ -1667,13 +1623,13 @@ class MailThread(models.AbstractModel): return result @api.multi - def message_unsubscribe(self, partner_ids): + def message_unsubscribe(self, partner_ids=None, channel_ids=None): """ Remove partners from the records followers. """ # not necessary for computation, but saves an access right check - if not partner_ids: + if not partner_ids and not channel_ids: return True user_pid = self.env.user.partner_id.id - if set(partner_ids) == set([user_pid]): + if not channel_ids and set(partner_ids) == set([user_pid]): self.check_access_rights('read') self.check_access_rule('read') else: @@ -1682,7 +1638,9 @@ class MailThread(models.AbstractModel): self.env['mail.followers'].sudo().search([ ('res_model', '=', self._name), ('res_id', 'in', self.ids), - ('partner_id', 'in', partner_ids) + '|', + ('partner_id', 'in', partner_ids or []), + ('channel_id', 'in', channel_ids or []) ]).unlink() @api.model @@ -1808,7 +1766,7 @@ class MailThread(models.AbstractModel): for pid, subtypes in new_followers.items(): subtypes = list(subtypes) if subtypes is not None else None - self.message_subscribe([pid], subtypes) + self.message_subscribe([pid], subtype_ids=subtypes) self._message_auto_subscribe_notify(user_pids) diff --git a/addons/mail/models/res_users.py b/addons/mail/models/res_users.py index 34d4d73092a..c3edd006130 100644 --- a/addons/mail/models/res_users.py +++ b/addons/mail/models/res_users.py @@ -121,7 +121,7 @@ class Users(models.Model): def message_update(self, msg_dict, update_vals=None): return True - def message_subscribe(self, partner_ids, subtype_ids=None): + def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True): return True @api.cr_uid_context diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 701bbffbeca..7193db4a9a2 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -7,8 +7,8 @@ access_mail_mail_portal,mail.mail.portal,model_mail_mail,base.group_portal,1,1,1 access_mail_mail_user,mail.mail.user,model_mail_mail,base.group_user,1,1,1,0 access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1 access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0 -access_mail_followers_portal,mail.followers.portal,model_mail_followers,base.group_portal,1,1,0,0 -access_mail_followers_user,mail.followers.user,model_mail_followers,base.group_user,1,1,0,0 +access_mail_followers_portal,mail.followers.portal,model_mail_followers,base.group_portal,1,1,1,0 +access_mail_followers_user,mail.followers.user,model_mail_followers,base.group_user,1,1,1,0 access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1 access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,0,0 access_mail_notification_portal,mail.notification.portal,model_mail_notification,base.group_portal,1,1,1,0 diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index c05d434c789..642975ea7a5 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -7,17 +7,18 @@ Mail.channel: access only public and joined groups - ['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.partner_id.id]), '&', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])] + ['|', '|', ('public', '=', 'public'), ('message_partner_ids', 'in', [user.partner_id.id]), '&', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])] - mail.followers: read and write its own entries + mail.followers: write its own entries [('partner_id', '=', user.partner_id.id)] + diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js index 15584c518b5..7f916dd3df9 100644 --- a/addons/mail/static/src/js/mail_followers.js +++ b/addons/mail/static/src/js/mail_followers.js @@ -77,7 +77,7 @@ var Followers = form_common.AbstractField.extend({ self.do_follow(); } else { - self.do_unfollow(); + self.do_unfollow([session.uid]); } }); @@ -145,15 +145,10 @@ var Followers = form_common.AbstractField.extend({ }, on_remove_follower: function (event) { - var partner_id = $(event.target).data('id'); - var name = $(event.target).parent().find("a").html(); - if (confirm(_.str.sprintf(_t("Warning! \n %s won't be notified of any email or discussion on this document. Do you really want to remove him from the followers ?"), name))) { - var context = new data.CompoundContext(this.build_context(), {}); - return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id], - [partner_id], - context]) - .then(this.proxy('read_value')); - } + var res_model = $(event.target).parent().find('a').data('res-model'); + var res_id = $(event.target).parent().find('a').data('res-id'); + if (res_model == 'res.partner') { return this.do_unfollow(undefined, [res_id], undefined); } + else { return this.do_unfollow(undefined, undefined, [res_id]); } }, on_follower_clicked: function (event) { @@ -245,19 +240,13 @@ var Followers = form_common.AbstractField.extend({ // truncate number of displayed followers _(this.followers).each(function (record) { - var partner = { - 'id': record['id'], - 'name': record['name'], - 'is_uid': record['is_uid'], - 'is_editable': record['is_editable'], - 'avatar_url': mail_utils.get_image(session, 'res.partner', 'image_small', record['id']), - } - - $(qweb.render('mail_followers_partner', {'record': partner, 'widget': self})) - .appendTo(node_user_list); + $(qweb.render('mail_followers_partner', { + 'record': _.extend(record, {'avatar_url': mail_utils.get_image(session, record['res_model'], 'image_small', record['id'])}), + 'widget': self}) + ).appendTo(node_user_list); // On mouse-enter it will show the edit_subtype pencil. - if (partner.is_editable) { + if (record.is_editable) { self.$('.o_timeline_follower_list').on('mouseenter mouseleave', function(e) { self.$('.o_timeline_edit_subtype') .toggleClass('oe_hidden', e.type == 'mouseleave'); @@ -358,26 +347,33 @@ var Followers = form_common.AbstractField.extend({ $(record).attr('checked', 'checked'); }); }, - - do_unfollow: function (user_pid) { - if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) { + + do_unfollow: function (user_ids, partner_ids, channel_ids) { + if (confirm(_t("Warning! \n If you remove a follower, he won't be notified of any email or discussion on this document. Do you really want to remove this follower ?"))) { _(this.$('.o_timeline_msg_subtype_check')).each(function (record) { $(record).attr('checked',false); }); - var action_unsubscribe = 'message_unsubscribe_users'; this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', true); - var follower_ids = [session.uid]; - - if (user_pid) { - action_unsubscribe = 'message_unsubscribe'; - follower_ids = [user_pid]; - } - var context = new data.CompoundContext(this.build_context(), {}); - return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id], - follower_ids, context]) - .then(this.proxy('read_value')); + + if (partner_ids || channel_ids) { + return this.ds_model.call( + 'message_unsubscribe', [ + [this.view.datarecord.id], + partner_ids, + channel_ids, + context] + ).then(this.proxy('read_value')); + } + else { + return this.ds_model.call( + 'message_unsubscribe_users', [ + [this.view.datarecord.id], + user_ids, + context] + ).then(this.proxy('read_value')); + } } return false; }, @@ -404,7 +400,7 @@ var Followers = form_common.AbstractField.extend({ }); if (!checklist.length) { - if (!this.do_unfollow(user_pid)) { + if (!this.do_unfollow(undefined, [user_pid], undefined)) { $(event.target).attr("checked", "checked"); } else { diff --git a/addons/mail/static/src/xml/mail_followers.xml b/addons/mail/static/src/xml/mail_followers.xml index e38fa8f480d..dd6d9883abd 100644 --- a/addons/mail/static/src/xml/mail_followers.xml +++ b/addons/mail/static/src/xml/mail_followers.xml @@ -42,14 +42,17 @@ - + t-att-title="record.name" + t-att-data-res-model="record.res_model" + t-att-data-res-id="record.res_id" + t-att-data-id="record.id"> + + title="Remove this follower"/> @@ -66,7 +69,7 @@