diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 7e5ee7c18f8..2fe06683269 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -1396,13 +1396,14 @@ class Meeting(models.Model): return super(Meeting, self.browse(thread_id)).message_post(**kwargs) @api.multi - def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True): + def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None): records = self.browse(get_real_ids(self.ids)) - return super(Meeting, records).message_subscribe( - partner_ids=partner_ids, - channel_ids=channel_ids, - subtype_ids=subtype_ids, - force=force) + return super(Meeting, records).message_subscribe(partner_ids=partner_ids, channel_ids=channel_ids, subtype_ids=subtype_ids) + + @api.multi + def _message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, customer_ids=None): + records = self.browse(get_real_ids(self.ids)) + return super(Meeting, records)._message_subscribe(partner_ids=partner_ids, channel_ids=channel_ids, subtype_ids=subtype_ids, customer_ids=customer_ids) @api.multi def message_unsubscribe(self, partner_ids=None, channel_ids=None): diff --git a/addons/crm/tests/test_new_lead_notification.py b/addons/crm/tests/test_new_lead_notification.py index 20f958e25c7..7c9b6d151a3 100644 --- a/addons/crm/tests/test_new_lead_notification.py +++ b/addons/crm/tests/test_new_lead_notification.py @@ -17,8 +17,7 @@ class NewLeadNotification(TestCrmCases): }) subtype = self.env.ref("crm.mt_salesteam_lead") - sales_team_1.message_subscribe(partner_ids=[self.crm_salesman.partner_id.id], subtype_ids=[subtype.id]) - sales_team_1.message_subscribe(channel_ids=[channel_listen.id], subtype_ids=[subtype.id]) + sales_team_1.message_subscribe(partner_ids=[self.crm_salesman.partner_id.id], channel_ids=[channel_listen.id], subtype_ids=[subtype.id]) # Imitate what happens in the controller when somebody creates a new # lead from the website form diff --git a/addons/hr/models/hr.py b/addons/hr/models/hr.py index 60fa38e7b14..eeec14e12cf 100644 --- a/addons/hr/models/hr.py +++ b/addons/hr/models/hr.py @@ -286,7 +286,7 @@ class Department(models.Model): department = super(Department, self.with_context(mail_create_nosubscribe=True)).create(vals) manager = self.env['hr.employee'].browse(vals.get("manager_id")) if manager.user_id: - department.message_subscribe_users(user_ids=manager.user_id.ids) + department.message_subscribe(partner_ids=manager.user_id.partner_id.ids) return department @api.multi @@ -303,7 +303,7 @@ class Department(models.Model): manager = self.env['hr.employee'].browse(manager_id) # subscribe the manager user if manager.user_id: - self.message_subscribe_users(user_ids=manager.user_id.ids) + self.message_subscribe(partner_ids=manager.user_id.partner_id.ids) # set the employees's parent to the new manager self._update_employee_manager(manager_id) return super(Department, self).write(vals) diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index f021eb4d10f..cc827d6fce7 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -527,18 +527,10 @@ class HrExpenseSheet(models.Model): @api.model def create(self, vals): - self._create_set_followers(vals) sheet = super(HrExpenseSheet, self).create(vals) sheet.activity_update() return sheet - @api.multi - def write(self, vals): - res = super(HrExpenseSheet, self).write(vals) - if vals.get('employee_id'): - self._add_followers() - return res - @api.multi def unlink(self): for expense in self: @@ -561,41 +553,14 @@ class HrExpenseSheet(models.Model): return 'hr_expense.mt_expense_paid' return super(HrExpenseSheet, self)._track_subtype(init_values) - def _get_users_to_subscribe(self, employee=False): - users = self.env['res.users'] - employee = employee or self.employee_id - if employee.user_id: - users |= employee.user_id - if employee.parent_id: - users |= employee.parent_id.user_id - if employee.department_id and employee.department_id.manager_id and employee.parent_id != employee.department_id.manager_id: - users |= employee.department_id.manager_id.user_id - return users - - def _add_followers(self): - users = self._get_users_to_subscribe() - self.message_subscribe_users(user_ids=users.ids) - - @api.model - def _create_set_followers(self, values): - # Add the followers at creation, so they can be notified - employee_id = values.get('employee_id') - if not employee_id: - return - - employee = self.env['hr.employee'].browse(employee_id) - users = self._get_users_to_subscribe(employee=employee) - self.env.user - values['message_follower_ids'] = [] - MailFollowers = self.env['mail.followers'] - for partner in users.mapped('partner_id'): - values['message_follower_ids'] += MailFollowers._add_follower_command(self._name, [], {partner.id: None}, {})[0] - - if values.get('user_id') and values.get('user_id') != employee.user_id.id and values.get('user_id') != self.env.uid: - resp_partner = self.env['res.users'].browse(values['user_id']) - # Parsing ORM command to avoid adding several times the same partner - # TDE note: a better implementation should come in saas 11.3 or 11.4, allowing to normally remove that code - if resp_partner.partner_id.id not in (x[2].get('partner_id') for x in values['message_follower_ids'] if len(x) == 3): - values['message_follower_ids'] += MailFollowers._add_follower_command(self._name, [], {resp_partner.partner_id.id: None}, {})[0] + def _message_auto_subscribe_followers(self, updated_values, subtype_ids): + res = super(HrExpenseSheet, self)._message_auto_subscribe_followers(updated_values, subtype_ids) + if updated_values.get('employee_id'): + employee = self.env['hr.employee'].browse(updated_values['employee_id']) + users = employee.user_id | employee.parent_id.user_id | employee.department_id.manager_id.user_id + for pid in users.mapped('partner_id').ids: + res.append((pid, subtype_ids, False)) + return res # -------------------------------------------- # Actions diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 03c90176363..5a79fef7c54 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -223,7 +223,7 @@ class HolidaysRequest(models.Model): def add_follower(self, employee_id): employee = self.env['hr.employee'].browse(employee_id) if employee.user_id: - self.message_subscribe_users(user_ids=employee.user_id.ids) + self.message_subscribe(partner_ids=employee.user_id.partner_id.ids) @api.multi @api.constrains('holiday_status_id') diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index fd98213e091..45b0057d889 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -124,7 +124,7 @@ class HolidaysAllocation(models.Model): def add_follower(self, employee_id): employee = self.env['hr.employee'].browse(employee_id) if employee.user_id: - self.message_subscribe_users(user_ids=employee.user_id.ids) + self.message_subscribe(partner_ids=employee.user_id.partner_id.ids) @api.multi @api.constrains('holiday_status_id') diff --git a/addons/hr_maintenance/models/equipment.py b/addons/hr_maintenance/models/equipment.py index 717275d89a9..ebfa6eb03fe 100644 --- a/addons/hr_maintenance/models/equipment.py +++ b/addons/hr_maintenance/models/equipment.py @@ -36,29 +36,29 @@ class MaintenanceEquipment(models.Model): def create(self, vals): equipment = super(MaintenanceEquipment, self).create(vals) # subscribe employee or department manager when equipment assign to him. - user_ids = [] + partner_ids = [] if equipment.employee_id and equipment.employee_id.user_id: - user_ids.append(equipment.employee_id.user_id.id) + partner_ids.append(equipment.employee_id.user_id.partner_id.id) if equipment.department_id and equipment.department_id.manager_id and equipment.department_id.manager_id.user_id: - user_ids.append(equipment.department_id.manager_id.user_id.id) - if user_ids: - equipment.message_subscribe_users(user_ids=user_ids) + partner_ids.append(equipment.department_id.manager_id.user_id.partner_id.id) + if partner_ids: + equipment.message_subscribe(partner_ids=partner_ids) return equipment @api.multi def write(self, vals): - user_ids = [] + partner_ids = [] # subscribe employee or department manager when equipment assign to employee or department. if vals.get('employee_id'): user_id = self.env['hr.employee'].browse(vals['employee_id'])['user_id'] if user_id: - user_ids.append(user_id.id) + partner_ids.append(user_id.partner_id.id) if vals.get('department_id'): department = self.env['hr.department'].browse(vals['department_id']) if department and department.manager_id and department.manager_id.user_id: - user_ids.append(department.manager_id.user_id.id) - if user_ids: - self.message_subscribe_users(user_ids=user_ids) + partner_ids.append(department.manager_id.user_id.partner_id.id) + if partner_ids: + self.message_subscribe(partner_ids=partner_ids) return super(MaintenanceEquipment, self).write(vals) @api.multi @@ -105,7 +105,7 @@ class MaintenanceRequest(models.Model): def create(self, vals): result = super(MaintenanceRequest, self).create(vals) if result.employee_id.user_id: - result.message_subscribe_users(user_ids=[result.employee_id.user_id.id]) + result.message_subscribe(partner_ids=[result.employee_id.user_id.partner_id.id]) return result @api.multi @@ -113,7 +113,7 @@ class MaintenanceRequest(models.Model): if vals.get('employee_id'): employee = self.env['hr.employee'].browse(vals['employee_id']) if employee and employee.user_id: - self.message_subscribe_users(user_ids=[employee.user_id.id]) + self.message_subscribe(partner_ids=[employee.user_id.partner_id.id]) return super(MaintenanceRequest, self).write(vals) @api.model diff --git a/addons/mail/models/ir_actions.py b/addons/mail/models/ir_actions.py index a8faa2703b8..bd4b03f3a4c 100644 --- a/addons/mail/models/ir_actions.py +++ b/addons/mail/models/ir_actions.py @@ -40,7 +40,7 @@ class ServerActions(models.Model): Model = self.env[action.model_id.model] if self.partner_ids or self.channel_ids and hasattr(Model, 'message_subscribe'): records = Model.browse(self._context.get('active_ids', self._context.get('active_id'))) - records.message_subscribe(self.partner_ids.ids, self.channel_ids.ids, force=False) + records.message_subscribe(self.partner_ids.ids, self.channel_ids.ids) return False @api.model diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py index 281e6f1c5b4..fc07e4e3ae7 100644 --- a/addons/mail/models/mail_followers.py +++ b/addons/mail/models/mail_followers.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import itertools + from odoo import api, fields, models @@ -33,83 +35,6 @@ class Followers(models.Model): '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(partner_data.values()) and all(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', list(partner_data)), ('channel_id', 'in', list(channel_data))]) - - 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, _internal_subtypes, external_subtypes = \ - self.env['mail.message.subtype'].default_subtypes(res_model) - - if force_mode: - employee_pids = self.env['res.users'].sudo().search([('partner_id', 'in', list(partner_data)), ('share', '=', False)]).mapped('partner_id').ids - for pid, data in partner_data.items(): - if not data: - if pid not in employee_pids: - partner_data[pid] = external_subtypes.ids - else: - partner_data[pid] = default_subtypes.ids - for cid, data in channel_data.items(): - if not data: - channel_data[cid] = default_subtypes.ids - - # create new followers, batch ok - gen_new_pids = [pid for pid in partner_data if pid not in p_exist] - gen_new_cids = [cid for cid in channel_data 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) - set([sub.partner_id.id for sub in doc_followers if sub.partner_id]) - set(gen_new_pids) - new_cids = set(channel_data) - 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. @@ -145,3 +70,175 @@ class Followers(models.Model): ('mail_followers_res_channel_res_model_id_uniq', 'unique(res_model,res_id,channel_id)', 'Error, a channel cannot follow twice the same object.'), ('partner_xor_channel', 'CHECK((partner_id IS NULL) != (channel_id IS NULL))', 'Error: A follower must be either a partner or a channel (but not both).') ] + + # -------------------------------------------------- + # Private tools methods to fetch followers data + # -------------------------------------------------- + + def _get_subscription_data(self, doc_data, pids, cids, include_pshare=False): + """ Private method allowing to fetch follower data from several documents of a given model. + Followers can be filtered given partner IDs and channel IDs. + + :param doc_data: list of pair (res_model, res_ids) that are the documents from which we + want to have subscription data; + :param pids: optional partner to filter; if None take all, otherwise limitate to pids + :param cids: optional channel to filter; if None take all, otherwise limitate to cids + :param include_pshare: optional join in partner to fetch their share status + + :return: list of followers data which is a list of tuples containing + follower ID, + document ID, + partner ID (void if channel_id), + channel ID (void if partner_id), + followed subtype IDs, + share status of partner (void id channel_id, returned only if include_pshare is True) + """ + # base query: fetch followers of given documents + where_clause = ' OR '.join(['fol.res_model = %s AND fol.res_id IN %s'] * len(doc_data)) + where_params = list(itertools.chain.from_iterable((rm, tuple(rids)) for rm, rids in doc_data)) + + # additional: filter on optional pids / cids + sub_where = [] + if pids: + sub_where += ["fol.partner_id IN %s"] + where_params.append(tuple(pids)) + elif pids is not None: + sub_where += ["fol.partner_id IS NULL"] + if cids: + sub_where += ["fol.channel_id IN %s"] + where_params.append(tuple(cids)) + elif cids is not None: + sub_where += ["fol.channel_id IS NULL"] + if sub_where: + where_clause += "AND (%s)" % " OR ".join(sub_where) + + query = """ +SELECT fol.id, fol.res_id, fol.partner_id, fol.channel_id, array_agg(subtype.id)%s +FROM mail_followers fol +%s +LEFT JOIN mail_followers_mail_message_subtype_rel fol_rel ON fol_rel.mail_followers_id = fol.id +LEFT JOIN mail_message_subtype subtype ON subtype.id = fol_rel.mail_message_subtype_id +WHERE %s +GROUP BY fol.id%s""" % ( + ', partner.partner_share' if include_pshare else '', + 'LEFT JOIN res_partner partner ON partner.id = fol.partner_id' if include_pshare else '', + where_clause, + ', partner.partner_share' if include_pshare else '') + self.env.cr.execute(query, tuple(where_params)) + return self.env.cr.fetchall() + + # -------------------------------------------------- + # Private tools methods to generate new subscription + # -------------------------------------------------- + + def _insert_followers(self, res_model, res_ids, partner_ids, partner_subtypes, channel_ids, channel_subtypes, + customer_ids=None, check_existing=False, existing_policy='skip'): + """ Main internal method allowing to create or update followers for documents, given a + res_model and the document res_ids. This method does not handle access rights. This is the + role of the caller to ensure there is no security breach. + + :param partner_subtypes: optional subtypes for new partner followers. If not given, default + ones are computed; + :param channel_subtypes: optional subtypes for new channel followers. If not given, default + ones are computed; + :param customer_ids: see ``_add_default_followers`` + :param check_existing: see ``_add_followers``; + :param existing_policy: see ``_add_followers``; + """ + sudo_self = self.sudo() + if not partner_subtypes and not channel_subtypes: # no subtypes -> default computation, no force, skip existing + new, upd = self._add_default_followers(res_model, res_ids, partner_ids, channel_ids, customer_ids=customer_ids) + else: + new, upd = self._add_followers(res_model, res_ids, partner_ids, partner_subtypes, channel_ids, channel_subtypes, check_existing=check_existing, existing_policy=existing_policy) + for res_id, values_list in new.items(): + for values in values_list: + sudo_self.create(dict(values, res_id=res_id)).id + for fol_id, values in upd.items(): + sudo_self.browse(fol_id).write(values) + + def _add_default_followers(self, res_model, res_ids, partner_ids, channel_ids=None, customer_ids=None): + """ Shortcut to ``_add_followers`` that computes default subtypes. Existing + followers are skipped as their subscription is considered as more important + compared to new default subscription. + + :param customer_ids: optional list of partner ids that are customers. It is used if computing + default subtype is necessary and allow to avoid the check of partners being customers (no + user or share user). It is just a matter of saving queries if the info is already known; + + :return: see ``_add_followers`` + """ + if not partner_ids and not channel_ids: + return dict(), dict() + + default, _, external = self.env['mail.message.subtype'].default_subtypes(res_model) + if partner_ids and customer_ids is None: + customer_ids = self.env['res.partner'].sudo().search([('id', 'in', partner_ids), ('partner_share', '=', True)]).ids + + c_stypes = dict.fromkeys(channel_ids or [], default.ids) + p_stypes = dict((pid, external.ids if pid in customer_ids else default.ids) for pid in partner_ids) + + return self._add_followers(res_model, res_ids, partner_ids, p_stypes, channel_ids, c_stypes, check_existing=True, existing_policy='skip') + + def _add_followers(self, res_model, res_ids, partner_ids, partner_subtypes, channel_ids, channel_subtypes, + check_existing=False, existing_policy='skip'): + """ Internal method that generates values to insert or update followers. Callers have to + handle the result, for example by making a valid ORM command, inserting or updating directly + follower records, ... This method returns two main data + + * first one is a dict which keys are res_ids. Value is a list of dict of values valid for + creating new followers for the related res_id; + * second one is a dict which keys are follower ids. Value is a dict of values valid for + updating the related follower record; + + :param check_existing: if True, check for existing followers for given documents and handle + them according to existing_policy parameter. Setting to False allows to save some computation + if caller is sure there are no conflict for followers; + :param existing policy: if check_existing, tells what to do with already-existing followers: + + * skip: simply skip existing followers, do not touch them; + * force: update existing with given subtypes only; + * update: gives an update dict allowing to add missing subtypes (no subtype removal); + """ + _res_ids = res_ids or [0] + data_fols, doc_pids, doc_cids = dict(), dict((i, set()) for i in _res_ids), dict((i, set()) for i in _res_ids) + + if check_existing and res_ids: + for fid, rid, pid, cid, sids in self._get_subscription_data([(res_model, res_ids)], partner_ids or None, channel_ids or None): + if existing_policy != 'force': + if pid: + doc_pids[rid].add(pid) + elif cid: + doc_cids[rid].add(cid) + data_fols[fid] = (rid, pid, cid, sids) + + if existing_policy == 'force': + self.sudo().browse(data_fols.keys()).unlink() + + new, update = dict(), dict() + for res_id in _res_ids: + for partner_id in set(partner_ids or []): + if partner_id not in doc_pids[res_id]: + new.setdefault(res_id, list()).append({ + 'res_model': res_model, + 'partner_id': partner_id, + 'subtype_ids': [(6, 0, partner_subtypes[partner_id])], + }) + elif existing_policy == 'update': + fol_id, sids = next(((key, val[3]) for key, val in data_fols.items() if val[0] == res_id and val[1] == partner_id), (False, [])) + new_sids = set(partner_subtypes[partner_id]) - set(sids) + if fol_id and new_sids: + update[fol_id] = {'subtype_ids': [(4, sid) for sid in new_sids]} + for channel_id in set(channel_ids or []): + if channel_id not in doc_cids[res_id]: + new.setdefault(res_id, list()).append({ + 'res_model': res_model, + 'channel_id': channel_id, + 'subtype_ids': [(6, 0, channel_subtypes[channel_id])], + }) + elif existing_policy == 'update': + fol_id, sids = next(((key, val[3]) for key, val in data_fols.items() if val[0] == res_id and val[2] == channel_id), (False, [])) + new_sids = set(channel_subtypes[channel_id]) - set(sids) + if fol_id and new_sids: + update[fol_id] = {'subtype_ids': [(4, sid) for sid in new_sids]} + + return new, update diff --git a/addons/mail/models/mail_message_subtype.py b/addons/mail/models/mail_message_subtype.py index f0bc054bd5e..0ebe7a86a11 100644 --- a/addons/mail/models/mail_message_subtype.py +++ b/addons/mail/models/mail_message_subtype.py @@ -56,17 +56,40 @@ class MailMessageSubtype(models.Model): self.clear_caches() return super(MailMessageSubtype, self).unlink() - @api.model - def auto_subscribe_subtypes(self, model_name): - """ Retrieve the header subtypes and relations for the given model. """ - subtype_ids, relations = self._auto_subscribe_subtypes(model_name) - return self.browse(subtype_ids), relations - @tools.ormcache('self.env.uid', 'model_name') - def _auto_subscribe_subtypes(self, model_name): - domain = ['|', ('res_model', '=', False), ('parent_id.res_model', '=', model_name)] - subtypes = self.search(domain) - return subtypes.ids, set(subtype.relation_field for subtype in subtypes if subtype.relation_field) + def _get_auto_subscription_subtypes(self, model_name): + """ Return data related to auto subscription based on subtype matching. + Example with tasks and project : + + * generic: discussion, res_model = False + * task: new, res_model = project.task + * project: task_new, parent_id = new, res_model = project.project, field = project_id + + Returned data + + * all_ids: all subtypes that are generic or related to task and project + * def_ids: for task, default subtypes ids + * int_ids: for task, internal-only default subtypes ids + * parent: dict(parent subtype id, child subtype id), i.e. {task_new.id: new.id} + * relation: dict(parent_model, relation_fields), i.e. {'project.project': ['project_id']} + """ + all_ids, def_ids, int_ids, parent, relation = list(), list(), list(), dict(), dict() + subtypes = self.sudo().search([ + '|', '|', ('res_model', '=', False), + ('res_model', '=', model_name), + ('parent_id.res_model', '=', model_name) + ]) + for subtype in subtypes: + if not subtype.res_model or subtype.res_model == model_name: + all_ids += subtype.ids + if subtype.default: + def_ids += subtype.ids + elif subtype.relation_field: + parent[subtype.id] = subtype.parent_id.id + relation.setdefault(subtype.res_model, set()).add(subtype.relation_field) + if subtype.internal: + int_ids += subtype.ids + return all_ids, def_ids, int_ids, parent, relation @api.model def default_subtypes(self, model_name): diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 1b52ec94e5d..82a917482e3 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -219,8 +219,8 @@ 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 += self.env['mail.followers']._add_follower_command(self._name, [], {self.env.user.partner_id.id: None}, {}, force=True)[0] + message_follower_ids = values.get('message_follower_ids') or [] + message_follower_ids += [(0, 0, fol_vals) for fol_vals in self.env['mail.followers']._add_default_followers(self._name, [], self.env.user.partner_id.ids, customer_ids=[])[0][0]] values['message_follower_ids'] = message_follower_ids thread = super(MailThread, self).create(values) @@ -234,7 +234,7 @@ class MailThread(models.AbstractModel): for key, val in self._context.items(): if key.startswith('default_') and key[8:] not in create_values: create_values[key[8:]] = val - thread.message_auto_subscribe(list(create_values), values=create_values) + thread._message_auto_subscribe(create_values) # track values if not self._context.get('mail_notrack'): @@ -271,7 +271,7 @@ class MailThread(models.AbstractModel): result = super(MailThread, self).write(values) # update followers - self.message_auto_subscribe(list(values), values=values) + self._message_auto_subscribe(values) # Perform the tracking if tracked_fields: @@ -1849,7 +1849,7 @@ class MailThread(models.AbstractModel): partner_to_subscribe = partner_ids if self._context.get('mail_post_autofollow_partner_ids'): partner_to_subscribe = [p for p in partner_ids if p in self._context.get('mail_post_autofollow_partner_ids')] - self.message_subscribe(list(partner_to_subscribe), force=False) + self.message_subscribe(list(partner_to_subscribe)) # _mail_flat_thread: automatically set free messages to the first posted message MailMessage = self.env['mail.message'] @@ -1908,7 +1908,7 @@ class MailThread(models.AbstractModel): # Post-process: subscribe author if values['author_id'] and values['model'] and self.ids and values['message_type'] != 'notification' and not self._context.get('mail_create_nosubscribe'): - self.message_subscribe([values['author_id']], force=False) + self._message_subscribe([values['author_id']]) @api.multi def message_post_with_view(self, views_or_xmlid, **kwargs): @@ -2034,26 +2034,18 @@ class MailThread(models.AbstractModel): # ------------------------------------------------------ @api.multi - def message_subscribe_users(self, user_ids=None, subtype_ids=None): - """ Wrapper on message_subscribe, using users. If user_ids is not - provided, subscribe uid instead. """ - if user_ids is None: - user_ids = [self._uid] - return self.message_subscribe(self.env['res.users'].browse(user_ids).mapped('partner_id').ids, subtype_ids=subtype_ids) - - @api.multi - 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 and not channel_ids: + def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None): + """ Main public API to add followers to a record set. Its main purpose is + to perform access rights checks before calling ``_message_subscribe``. """ + if not self or (not partner_ids and not channel_ids): return True - if partner_ids is None: - partner_ids = [] - if channel_ids is None: - channel_ids = [] - # TDE CHECK THIS - if not channel_ids and partner_ids and set(partner_ids) == set([self.env.user.partner_id.id]): + partner_ids = partner_ids or [] + channel_ids = channel_ids or [] + adding_current = set(partner_ids) == set([self.env.user.partner_id.id]) + customer_ids = [] if adding_current else None + + if not channel_ids and partner_ids and adding_current: try: self.check_access_rights('read') self.check_access_rule('read') @@ -2063,25 +2055,38 @@ class MailThread(models.AbstractModel): self.check_access_rights('write') self.check_access_rule('write') - 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({'message_follower_ids': part[record.id]}) + return self._message_subscribe(partner_ids, channel_ids, subtype_ids, customer_ids=customer_ids) + + def _message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, customer_ids=None): + """ Main private API to add followers to a record set. This method adds + partners and channels, given their IDs, as followers of all records + contained in the record set. + + If subtypes are given existing followers are erased with new subtypes. + If default one have to be computed only missing followers will be added + with default subtypes matching the record set model. + + This private method does not specifically check for access right. Use + ``message_subscribe`` public API when not sure about access rights. + + :param customer_ids: see ``_insert_followers`` """ + if not self: + return True + + if not subtype_ids: + self.env['mail.followers']._insert_followers( + self._name, self.ids, partner_ids, None, channel_ids, None, + customer_ids=customer_ids) + else: + self.env['mail.followers']._insert_followers( + self._name, self.ids, + partner_ids, dict((pid, subtype_ids) for pid in partner_ids), + channel_ids, dict((cid, subtype_ids) for cid in channel_ids), + customer_ids=customer_ids, check_existing=True, existing_policy='force') self.invalidate_cache() return True - @api.multi - def message_unsubscribe_users(self, user_ids=None): - """ Wrapper on message_subscribe, using users. If user_ids is not - provided, unsubscribe uid instead. """ - if user_ids is None: - user_ids = [self._uid] - partner_ids = [user.partner_id.id for user in self.env['res.users'].browse(user_ids)] - return self.message_unsubscribe(partner_ids) - @api.multi def message_unsubscribe(self, partner_ids=None, channel_ids=None): """ Remove partners from the records followers. """ @@ -2103,52 +2108,64 @@ class MailThread(models.AbstractModel): ('channel_id', 'in', channel_ids or []) ]).unlink() - @api.model - def _message_get_auto_subscribe_fields(self, updated_fields, auto_follow_fields=None): - """ Returns the list of relational fields linking to res.users that should - trigger an auto subscribe. The default list checks for the fields - - called 'user_id' - - linking to res.users - - with track_visibility set - In OpenERP V7, this is sufficent for all major addon such as opportunity, - project, issue, recruitment, sale. - Override this method if a custom behavior is needed about fields - that automatically subscribe users. + def _message_auto_subscribe_followers(self, updated_values, default_subtype_ids): + """ Optional method to override in addons inheriting from mail.thread. + Return a list tuples containing ( + partner ID, + subtype IDs (or False if model-based default subtypes), + QWeb template XML ID for notification (or False is no specific + notification is required), + ), aka partners and their subtype and possible notification to send + using the auto subscription mechanism linked to updated values. + + Default value of this method is to return the new responsible of + documents. This is done using relational fields linking to res.users + with track_visibility set. Since OpenERP v7 it is considered as being + responsible for the document and therefore standard behavior is to + subscribe the user and send him a notification. + + Override this method to change that behavior and/or to add people to + notify, using possible custom notification. + + :param updated_values: see ``_message_auto_subscribe`` + :param default_subtype_ids: coming from ``_get_auto_subscription_subtypes`` """ - if auto_follow_fields is None: - auto_follow_fields = ['user_id'] - user_field_lst = [] + fnames = [] for name, field in self._fields.items(): - if name in auto_follow_fields and name in updated_fields and getattr(field, 'track_visibility', False) and field.comodel_name == 'res.users': - user_field_lst.append(name) - return user_field_lst + if name == 'user_id' and updated_values.get(name) and getattr(field, 'track_visibility', False): + if field.comodel_name == 'res.users': + fnames.append(name) + + new_subscriptions = [] + user_ids = [updated_values[fname] for fname in fnames if updated_values[fname]] + if user_ids: + new_pids = self.env['res.partner'].sudo().search([('user_ids', 'in', user_ids), ('active', '=', True)]).ids + for new_pid in new_pids: + new_subscriptions.append((new_pid, default_subtype_ids, 'mail.message_user_assigned' if new_pid != self.env.user.partner_id.id else False)) + + return new_subscriptions @api.multi - def _message_auto_subscribe_notify(self, partner_ids): - """ Notify newly subscribed followers of the last posted message. - :param partner_ids : the list of partner to add as needaction partner of the last message - (This excludes the current partner) + def _message_auto_subscribe_notify(self, partner_ids, template): + """ Notify new followers, using a template to render the content of the + notification message. Notifications pushed are done using the standard + notification mechanism in mail.thread. It is either inbox either email + depending on the partner state: no user (email, customer), share user + (email, customer) or classic user (notification_type) + + :param partner_ids: IDs of partner to notify; + :param template: XML ID of template used for the notification; """ - if not partner_ids: + if not self or self.env.context.get('mail_auto_subscribe_no_notify'): return - if self.env.context.get('mail_auto_subscribe_no_notify'): - return - - # send the email only to the current record and not all the ids matching active_domain ! - # by default, send_mail for mass_mail use the active_domain instead of active_ids. - if 'active_domain' in self.env.context: - ctx = dict(self.env.context) - ctx.pop('active_domain') - self = self.with_context(ctx) - - assignation_tpl = self.env.ref('mail.message_user_assigned') + view = self.env['ir.ui.view'].browse(self.env['ir.model.data'].xmlid_to_res_id(template)) for record in self: values = { 'object': record, } - assignation_msg = assignation_tpl.render(values, engine='ir.qweb', minimal_qcontext=True) + assignation_msg = view.render(values, engine='ir.qweb', minimal_qcontext=True) assignation_msg = self.env['mail.thread']._replace_local_links(assignation_msg) record.message_notify( subject='You have been assigned to %s' % record.display_name, @@ -2162,75 +2179,65 @@ class MailThread(models.AbstractModel): ) @api.multi - def message_auto_subscribe(self, updated_fields, values=None): - """ Handle auto subscription. Two methods for auto subscription exist: + def _message_auto_subscribe(self, updated_values): + """ Handle auto subscription. Auto subscription is done based on two + main mechanisms - - tracked res.users relational fields, such as user_id fields. Those fields - must be relation fields toward a res.users record, and must have the - track_visilibity attribute set. - - using subtypes parent relationship: check if the current model being - modified has an header record (such as a project for tasks) whose followers - can be added as followers of the current records. Example of structure - with project and task: + * using subtypes parent relationship. For example following a parent record + (i.e. project) with subtypes linked to child records (i.e. task). See + mail.message.subtype ``_get_auto_subscription_subtypes``; + * calling _message_auto_subscribe_notify that returns a list of partner + to subscribe, as well as data about the subtypes and notification + to send. Base behavior is to subscribe responsible and notify them; - - st_project_1.parent_id = st_task_1 - - st_project_1.res_model = 'project.project' - - st_project_1.relation_field = 'project_id' - - st_task_1.model = 'project.task' + Adding application-specific auto subscription should be done by overriding + ``_message_auto_subscribe_followers``. It should return structured data + for new partner to subscribe, with subtypes and eventual notification + to perform. See that method for more details. - :param list updated_fields: list of updated fields to track - :param dict values: updated values; if None, the first record will be browsed - to get the values. Added after releasing 7.0, therefore - not merged with updated_fields argumment. + :param updated_values: values modifying the record trigerring auto subscription """ - new_partners, new_channels = dict(), dict() - - # fetch auto_follow_fields: res.users relation fields whose changes are tracked for subscription - user_field_lst = self._message_get_auto_subscribe_fields(updated_fields) - - # fetch header subtypes - subtypes, relation_fields = self.env['mail.message.subtype'].auto_subscribe_subtypes(self._name) - - # if no change in tracked field or no change in tracked relational field: quit - if not any(relation in relation_fields for relation in updated_fields) and not user_field_lst: + if not self: return True - # find followers of headers, update structure for new followers - headers = set() - for subtype in subtypes: - if subtype.relation_field and values.get(subtype.relation_field): - headers.add((subtype.res_model, values.get(subtype.relation_field))) - if headers: - header_domain = ['|'] * (len(headers) - 1) - for header in headers: - header_domain += ['&', ('res_model', '=', header[0]), ('res_id', '=', header[1])] - for header_follower in self.env['mail.followers'].sudo().search(header_domain): - for subtype in header_follower.subtype_ids: - if subtype.parent_id and subtype.parent_id.res_model == self._name: - new_subtype = subtype.parent_id - elif subtype.res_model is False: - new_subtype = subtype - else: - continue - if header_follower.partner_id: - new_partners.setdefault(header_follower.partner_id.id, set()).add(new_subtype.id) - else: - new_channels.setdefault(header_follower.channel_id.id, set()).add(new_subtype.id) + new_partners, new_channels = dict(), dict() - # add followers coming from res.users relational fields that are tracked - to_add_users = self.env['res.users'].sudo().browse([values[name] for name in user_field_lst if values.get(name)]).filtered(lambda u: u.partner_id.active) - for partner in to_add_users.mapped('partner_id'): - new_partners.setdefault(partner.id, None) + # fetch auto subscription subtypes data + updated_relation = dict() + all_ids, def_ids, int_ids, parent, relation = self.env['mail.message.subtype']._get_auto_subscription_subtypes(self._name) - for pid, subtypes in new_partners.items(): - subtypes = list(subtypes) if subtypes is not None else None - self.message_subscribe(partner_ids=[pid], subtype_ids=subtypes, force=(subtypes != None)) - for cid, subtypes in new_channels.items(): - subtypes = list(subtypes) if subtypes is not None else None - self.message_subscribe(channel_ids=[cid], subtype_ids=subtypes, force=(subtypes != None)) + # check effectively modified relation field + for res_model, fnames in relation.items(): + for field in (fname for fname in fnames if updated_values.get(fname)): + updated_relation.setdefault(res_model, set()).add(field) + udpated_fields = [fname for fnames in updated_relation.values() for fname in fnames if updated_values.get(fname)] - # remove the current user from the needaction partner to avoid to notify the author of the message - user_pids = [user.partner_id.id for user in to_add_users if user != self.env.user] - self._message_auto_subscribe_notify(user_pids) + if udpated_fields: + doc_data = [(model, [updated_values[fname] for fname in fnames]) for model, fnames in updated_relation.items()] + res = self.env['mail.followers']._get_subscription_data(doc_data, None, None, include_pshare=True) + for fid, rid, pid, cid, subtype_ids, pshare in res: + sids = [parent[sid] for sid in subtype_ids if parent.get(sid)] + sids += [sid for sid in subtype_ids if sid not in parent and sid in def_ids] + if pid: + new_partners[pid] = (set(sids) & set(all_ids)) - set(int_ids) if pshare else set(sids) & set(all_ids) + if cid: + new_channels[cid] = (set(sids) & set(all_ids)) - set(int_ids) + + notify_data = dict() + res = self._message_auto_subscribe_followers(updated_values, def_ids) + for pid, sids, template in res: + new_partners.setdefault(pid, sids) + if template: + notify_data.setdefault(template, list()).append(pid) + + self.env['mail.followers']._insert_followers( + self._name, self.ids, + list(new_partners), new_partners, + list(new_channels), new_channels, + check_existing=True, existing_policy='skip') + + # notify people from auto subscription, for example like assignation + for template, pids in notify_data.items(): + self._message_auto_subscribe_notify(pids, template) return True diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py index e324123595f..6f4e02b4814 100644 --- a/addons/maintenance/models/maintenance.py +++ b/addons/maintenance/models/maintenance.py @@ -208,13 +208,13 @@ class MaintenanceEquipment(models.Model): def create(self, vals): equipment = super(MaintenanceEquipment, self).create(vals) if equipment.owner_user_id: - equipment.message_subscribe_users(user_ids=[equipment.owner_user_id.id]) + equipment.message_subscribe(partner_ids=[equipment.owner_user_id.partner_id.id]) return equipment @api.multi def write(self, vals): if vals.get('owner_user_id'): - self.message_subscribe_users(user_ids=[vals['owner_user_id']]) + self.message_subscribe(partner_ids=self.env['res.users'].browse(vals['owner_user_id']).partner_id.ids) return super(MaintenanceEquipment, self).write(vals) @api.model @@ -377,8 +377,8 @@ class MaintenanceRequest(models.Model): def _add_followers(self): for request in self: - user_ids = (request.owner_user_id + request.technician_user_id).ids - request.message_subscribe_users(user_ids=user_ids) + partner_ids = (request.owner_user_id.partner_id + request.technician_user_id.partner_id).ids + request.message_subscribe(partner_ids=partner_ids) @api.model def _read_group_stage_ids(self, stages, domain, order): diff --git a/addons/project/models/project.py b/addons/project/models/project.py index e46bbd11a82..f0e6a3394ac 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -358,16 +358,14 @@ class Project(models.Model): return super(Project, self).get_access_action(access_uid) @api.multi - def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True): + def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None): """ Subscribe to all existing active tasks when subscribing to a project """ - res = super(Project, self).message_subscribe(partner_ids=partner_ids, channel_ids=channel_ids, subtype_ids=subtype_ids, force=force) - if not subtype_ids or any(subtype.parent_id.res_model == 'project.task' for subtype in self.env['mail.message.subtype'].browse(subtype_ids)): - for partner_id in partner_ids or []: - self.mapped('tasks').filtered(lambda task: not task.stage_id.fold and partner_id not in task.message_partner_ids.ids).message_subscribe( - partner_ids=[partner_id], channel_ids=None, subtype_ids=None, force=False) - for channel_id in channel_ids or []: - self.mapped('tasks').filtered(lambda task: not task.stage_id.fold and channel_id not in task.message_channel_ids.ids).message_subscribe( - partner_ids=None, channel_ids=[channel_id], subtype_ids=None, force=False) + res = super(Project, self).message_subscribe(partner_ids=partner_ids, channel_ids=channel_ids, subtype_ids=subtype_ids) + project_subtypes = self.env['mail.message.subtype'].browse(subtype_ids) if subtype_ids else None + task_subtypes = project_subtypes.mapped('parent_id').ids if project_subtypes else None + if not subtype_ids or task_subtypes: + self.mapped('tasks').message_subscribe( + partner_ids=partner_ids, channel_ids=channel_ids, subtype_ids=task_subtypes) return res @api.multi diff --git a/addons/project/tests/test_access_rights.py b/addons/project/tests/test_access_rights.py index 0016a71f71f..fe184147492 100644 --- a/addons/project/tests/test_access_rights.py +++ b/addons/project/tests/test_access_rights.py @@ -79,7 +79,7 @@ class TestPortalProject(TestPortalProjectBase): # Do: Donovan reads project -> ko (public ko employee) self.assertRaises(AccessError, pigs.sudo(self.user_public).read, ['user_id']) - pigs.message_subscribe_users(user_ids=[self.user_projectuser.id]) + pigs.message_subscribe(partner_ids=[self.user_projectuser.partner_id.id]) # Do: Alfred reads project -> ok (follower ok followers) prout = pigs.sudo(self.user_projectuser) @@ -93,7 +93,7 @@ class TestPortalProject(TestPortalProjectBase): 'name': 'Pigs task', 'project_id': pigs.id }) # not follower user should not be able to create a task - pigs.sudo(self.user_projectuser).message_unsubscribe_users(user_ids=[self.user_projectuser.id]) + pigs.sudo(self.user_projectuser).message_unsubscribe(partner_ids=[self.user_projectuser.partner_id.id]) self.assertRaises(AccessError, self.env['project.task'].sudo(self.user_projectuser).with_context({ 'mail_create_nolog': True}).create, {'name': 'Pigs task', 'project_id': pigs.id}) diff --git a/addons/project/tests/test_portal.py b/addons/project/tests/test_portal.py index 6413488da2e..ba18b5a5a16 100644 --- a/addons/project/tests/test_portal.py +++ b/addons/project/tests/test_portal.py @@ -25,9 +25,9 @@ class TestPortalProject(TestPortalProjectBase): self.assertRaises(AccessError, self.env['project.task'].sudo(self.user_noone).search, [('project_id', '=', pigs.id)]) # Data: task follower - pigs.sudo(self.user_projectmanager).message_subscribe_users(user_ids=[self.user_portal.id]) - self.task_1.sudo(self.user_projectuser).message_subscribe_users(user_ids=[self.user_portal.id]) - self.task_3.sudo(self.user_projectuser).message_subscribe_users(user_ids=[self.user_portal.id]) + pigs.sudo(self.user_projectmanager).message_subscribe(partner_ids=[self.user_portal.partner_id.id]) + self.task_1.sudo(self.user_projectuser).message_subscribe(partner_ids=[self.user_portal.partner_id.id]) + self.task_3.sudo(self.user_projectuser).message_subscribe(partner_ids=[self.user_portal.partner_id.id]) # Do: Chell reads project -> ok (portal ok public) pigs.sudo(self.user_portal).read(['user_id']) # Do: Donovan reads project -> ko (public ko portal) @@ -35,5 +35,5 @@ class TestPortalProject(TestPortalProjectBase): # Test: no access right to project.task self.assertRaises(AccessError, self.env['project.task'].sudo(self.user_public).search, []) # Data: task follower cleaning - self.task_1.sudo(self.user_projectuser).message_unsubscribe_users(user_ids=[self.user_portal.id]) - self.task_3.sudo(self.user_projectuser).message_unsubscribe_users(user_ids=[self.user_portal.id]) + self.task_1.sudo(self.user_projectuser).message_unsubscribe(partner_ids=[self.user_portal.partner_id.id]) + self.task_3.sudo(self.user_projectuser).message_unsubscribe(partner_ids=[self.user_portal.partner_id.id]) diff --git a/addons/test_mail/tests/test_mail_followers.py b/addons/test_mail/tests/test_mail_followers.py index 3d2600facce..20e94c288fd 100644 --- a/addons/test_mail/tests/test_mail_followers.py +++ b/addons/test_mail/tests/test_mail_followers.py @@ -26,7 +26,7 @@ class BaseFollowersTest(common.BaseFunctionalTest): test_record = self.test_record.sudo(self.user_employee) followed_before = test_record.search([('message_is_follower', '=', True)]) self.assertFalse(test_record.message_is_follower) - test_record.message_subscribe_users(user_ids=[self.user_employee.id]) + test_record.message_subscribe(partner_ids=[self.user_employee.partner_id.id]) followed_after = test_record.search([('message_is_follower', '=', True)]) self.assertTrue(test_record.message_is_follower) self.assertEqual(followed_before | test_record, followed_after) @@ -80,7 +80,7 @@ class BaseFollowersTest(common.BaseFunctionalTest): self.assertEqual(follower, test_record.message_follower_ids) self.assertEqual(follower.subtype_ids, self.mt_mg_nodef) - def test_followers_multiple_subscription(self): + def test_followers_multiple_subscription_force(self): test_record = self.test_record.sudo(self.user_employee) test_record.message_subscribe(partner_ids=[self.user_admin.partner_id.id], subtype_ids=[self.mt_mg_nodef.id]) @@ -102,7 +102,7 @@ class BaseFollowersTest(common.BaseFunctionalTest): self.assertEqual(test_record.message_follower_ids.subtype_ids, self.mt_mg_nodef | self.mt_al_nodef) # set new subtypes with force=False, meaning no rewriting of the subscription is done -> result should not change - test_record.message_subscribe(partner_ids=[self.user_admin.partner_id.id], force=False) + test_record.message_subscribe(partner_ids=[self.user_admin.partner_id.id]) self.assertEqual(test_record.message_partner_ids, self.user_admin.partner_id) self.assertEqual(test_record.message_channel_ids, self.env['mail.channel']) self.assertEqual(test_record.message_follower_ids.subtype_ids, self.mt_mg_nodef | self.mt_al_nodef) diff --git a/addons/test_mail/tests/test_message_compose.py b/addons/test_mail/tests/test_message_compose.py index 08b7760f3bd..517f0faeb1c 100644 --- a/addons/test_mail/tests/test_message_compose.py +++ b/addons/test_mail/tests/test_message_compose.py @@ -32,7 +32,7 @@ class TestMessagePost(BaseFunctionalTest, MockEmails, TestRecipients): _body, _body_alt, _subject = '

Test Body

', 'Test Body', 'Test Subject' # subscribe second employee to the group to test notifications - self.test_record.message_subscribe_users(user_ids=[self.user_admin.id]) + self.test_record.message_subscribe(partner_ids=[self.user_admin.partner_id.id]) msg = self.test_record.sudo(self.user_employee).message_post( body=_body, subject=_subject, @@ -61,7 +61,7 @@ class TestMessagePost(BaseFunctionalTest, MockEmails, TestRecipients): @mute_logger('odoo.addons.mail.models.mail_mail') def test_post_notifications_keep_emails(self): - self.test_record.message_subscribe_users(user_ids=[self.user_admin.id]) + self.test_record.message_subscribe(partner_ids=[self.user_admin.partner_id.id]) msg = self.test_record.sudo(self.user_employee).message_post( body='Test', subject='Test', diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index b8e3b71d249..f399f812041 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -93,13 +93,13 @@ class TestMailPerformance(TransactionCase): @warmup def test_create_mail_with_tracking(self): """ Create records inheriting from 'mail.thread' (with field tracking). """ - with self.assertQueryCount(admin=16, demo=16): # test_mail only: 16 - 16 + with self.assertQueryCount(admin=15, demo=15): # test_mail only: 15 - 15 self.env['test_performance.mail'].create({'name': 'X'}) @users('admin', 'emp') @warmup def test_create_mail_simple(self): - with self.assertQueryCount(admin=9, emp=9): # test_mail only: 9 - 9 + with self.assertQueryCount(admin=8, emp=8): # test_mail only: 8 - 8 self.env['mail.test.simple'].create({'name': 'Test'}) @users('admin', 'emp') @@ -148,7 +148,7 @@ class TestAdvMailPerformance(TransactionCase): def test_adv_activity(self): model = self.env['mail.test.activity'] - with self.assertQueryCount(admin=9, emp=9): # test_mail only: 9 - 9 + with self.assertQueryCount(admin=8, emp=8): # test_mail only: 8 - 8 model.create({'name': 'Test'}) @users('admin', 'emp') @@ -160,7 +160,7 @@ class TestAdvMailPerformance(TransactionCase): 'default_res_model': 'mail.test.activity', }) - with self.assertQueryCount(admin=27, emp=33): # test_mail only: 26 - 32 + with self.assertQueryCount(admin=11, emp=15): # test_mail only: 11 - 15 activity = MailActivity.create({ 'summary': 'Test Activity', 'res_id': record.id, @@ -176,7 +176,7 @@ class TestAdvMailPerformance(TransactionCase): def test_adv_activity_mixin(self): record = self.env['mail.test.activity'].create({'name': 'Test'}) - with self.assertQueryCount(admin=27, emp=33): # test_mail only: 26 - 32 + with self.assertQueryCount(admin=11, emp=15): # test_mail only: 11 - 15 record.action_start('Test Start') record.write({'name': 'Dupe write'}) @@ -193,7 +193,7 @@ class TestAdvMailPerformance(TransactionCase): self.user_test.write({'notification_type': 'email'}) record = self.env['mail.test.track'].create({'name': 'Test'}) - with self.assertQueryCount(admin=88, emp=115): # test_mail only: 86 - 113 + with self.assertQueryCount(admin=82, emp=107): # test_mail only: 80 - 105 record.write({ 'user_id': self.user_test.id, }) @@ -203,7 +203,7 @@ class TestAdvMailPerformance(TransactionCase): def test_message_assignation_inbox(self): record = self.env['mail.test.track'].create({'name': 'Test'}) - with self.assertQueryCount(admin=48, emp=63): # test_mail only: 48 - 63 + with self.assertQueryCount(admin=42, emp=55): # test_mail only: 42 - 55 record.write({ 'user_id': self.user_test.id, }) @@ -223,7 +223,7 @@ class TestAdvMailPerformance(TransactionCase): def test_message_log_with_post(self): record = self.env['mail.test.simple'].create({'name': 'Test'}) - with self.assertQueryCount(admin=27, emp=38): # test_mail only: 27 - 38 + with self.assertQueryCount(admin=24, emp=34): # test_mail only: 24 - 34 record.message_post( body='

Test message_post as log

', subtype='mail.mt_note', @@ -234,7 +234,7 @@ class TestAdvMailPerformance(TransactionCase): def test_message_post_no_notification(self): record = self.env['mail.test.simple'].create({'name': 'Test'}) - with self.assertQueryCount(admin=27, emp=38): # test_mail only: 27 - 38 + with self.assertQueryCount(admin=24, emp=34): # test_mail only: 24 - 34 record.message_post( body='

Test Post Performances basic

', partner_ids=[], @@ -247,7 +247,7 @@ class TestAdvMailPerformance(TransactionCase): def test_message_post_one_email_notification(self): record = self.env['mail.test.simple'].create({'name': 'Test'}) - with self.assertQueryCount(admin=78, emp=105): # com runbot 76 - 103 // test_mail only: 76 - 103 + with self.assertQueryCount(admin=75, emp=101): # test_mail only: 73 - 99 record.message_post( body='

Test Post Performances with an email ping

', partner_ids=self.customer.ids, @@ -259,7 +259,7 @@ class TestAdvMailPerformance(TransactionCase): def test_message_post_one_inbox_notification(self): record = self.env['mail.test.simple'].create({'name': 'Test'}) - with self.assertQueryCount(admin=40, emp=52): # com runbot 38 - 50 // test_mail only: 40 - 52 + with self.assertQueryCount(admin=37, emp=48): # com runbot 35 - 46 // test_mail only: 37 - 48 record.message_post( body='

Test Post Performances with an inbox ping

', partner_ids=self.user_test.partner_id.ids, @@ -272,10 +272,10 @@ class TestAdvMailPerformance(TransactionCase): def test_message_subscribe_default(self): record = self.env['mail.test.simple'].create({'name': 'Test'}) - with self.assertQueryCount(admin=7, emp=8): # test_mail only: 7 - 8 + with self.assertQueryCount(admin=6, emp=6): # test_mail only: 6 - 6 record.message_subscribe(partner_ids=self.user_test.partner_id.ids) - with self.assertQueryCount(admin=19, emp=19): # test_mail only: 19 - 19 + with self.assertQueryCount(admin=3, emp=3): # test_mail only: 3 - 3 record.message_subscribe(partner_ids=self.user_test.partner_id.ids) @mute_logger('odoo.models.unlink') @@ -285,10 +285,10 @@ class TestAdvMailPerformance(TransactionCase): record = self.env['mail.test.simple'].create({'name': 'Test'}) subtype_ids = (self.env.ref('test_mail.st_mail_test_simple_external') | self.env.ref('mail.mt_comment')).ids - with self.assertQueryCount(admin=7, emp=8): # test_mail only: 7 - 8 + with self.assertQueryCount(admin=5, emp=5): # test_mail only: 5 - 5 record.message_subscribe(partner_ids=self.user_test.partner_id.ids, subtype_ids=subtype_ids) - with self.assertQueryCount(admin=19, emp=19): # test_mail only: 18 - 18 + with self.assertQueryCount(admin=14, emp=14): # test_mail only: 14 - 14 record.message_subscribe(partner_ids=self.user_test.partner_id.ids, subtype_ids=subtype_ids) @@ -424,35 +424,33 @@ class TestHeavyMailPerformance(TransactionCase): self.assertEqual(rec.message_channel_ids, self.env['mail.channel']) # subscribe new followers with forced given subtypes - with self.assertQueryCount(admin=20, emp=21): # test_mail only: 20 - 21 + with self.assertQueryCount(admin=16, emp=16): # test_mail only: 16 - 16 rec.message_subscribe( partner_ids=pids[:4], channel_ids=cids, - subtype_ids=subtype_ids, + subtype_ids=subtype_ids ) self.assertEqual(rec.message_partner_ids, self.env.user.partner_id | self.user_portal.partner_id | self.partners[:4]) self.assertEqual(rec.message_channel_ids, self.channel) # subscribe existing and new followers with force=False, meaning only some new followers will be added - with self.assertQueryCount(admin=11, emp=12): # test_mail only: 11 - 12 + with self.assertQueryCount(admin=8, emp=8): # test_mail only: 8 - 8 rec.message_subscribe( partner_ids=pids[:6], channel_ids=cids, - subtype_ids=None, - force=False, + subtype_ids=None ) self.assertEqual(rec.message_partner_ids, self.env.user.partner_id | self.user_portal.partner_id | self.partners[:6]) self.assertEqual(rec.message_channel_ids, self.channel) # subscribe existing and new followers with force=True, meaning all will have the same subtypes - with self.assertQueryCount(admin=48, emp=49): # test_mail only: 48 - 49 + with self.assertQueryCount(admin=42, emp=43): # test_mail only: 42 - 43 rec.message_subscribe( partner_ids=pids, channel_ids=cids, - subtype_ids=subtype_ids, - force=True, + subtype_ids=subtype_ids ) self.assertEqual(rec.message_partner_ids, self.env.user.partner_id | self.user_portal.partner_id | self.partners) @@ -471,7 +469,7 @@ class TestHeavyMailPerformance(TransactionCase): }) self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id) - with self.assertQueryCount(admin=90, emp=119): # test_mail only: 88 - 117 + with self.assertQueryCount(admin=84, emp=111): # test_mail only: 82 - 109 rec.write({'user_id': self.user_portal.id}) self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id) @@ -494,7 +492,7 @@ class TestHeavyMailPerformance(TransactionCase): customer_id = self.customer.id user_id = self.user_portal.id - with self.assertQueryCount(admin=319, emp=380): # com runbot: 319 - 179 // test_mail only: 312 - 373 + with self.assertQueryCount(admin=237, emp=286): # test_mail only: 230 - 279 rec = self.env['mail.test.full'].create({ 'name': 'Test', 'umbrella_id': umbrella_id, @@ -523,7 +521,7 @@ class TestHeavyMailPerformance(TransactionCase): }) self.assertEqual(rec.message_partner_ids, self.user_portal.partner_id | self.env.user.partner_id) - with self.assertQueryCount(admin=227, emp=264): # test_mail only: 223 - 260 + with self.assertQueryCount(admin=149, emp=173): # test_mail only: 144 - 168 rec.write({ 'name': 'Test2', 'umbrella_id': self.umbrella.id, @@ -561,7 +559,7 @@ class TestHeavyMailPerformance(TransactionCase): }) self.assertEqual(rec.message_partner_ids, self.user_portal.partner_id | self.env.user.partner_id) - with self.assertQueryCount(admin=230, emp=268): # test_mail only: 225 - 264 + with self.assertQueryCount(admin=155, emp=183): # test_mail only: 150 - 178 rec.write({ 'name': 'Test2', 'umbrella_id': umbrella_id, @@ -595,7 +593,7 @@ class TestHeavyMailPerformance(TransactionCase): }) self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id) - with self.assertQueryCount(admin=63, emp=90): # test_mail only: 61 - 88 + with self.assertQueryCount(admin=61, emp=84): # test_mail only: 59 - 82 rec.write({ 'name': 'Test2', 'customer_id': customer_id, diff --git a/addons/website_event_track/controllers/main.py b/addons/website_event_track/controllers/main.py index eaf17861741..ec7d6b806df 100644 --- a/addons/website_event_track/controllers/main.py +++ b/addons/website_event_track/controllers/main.py @@ -135,7 +135,7 @@ class WebsiteEventTrackController(http.Controller): 'description': escape(post['description']) }) if request.env.user != request.website.user_id: - track.sudo().message_subscribe_users(user_ids=request.env.user.ids) + track.sudo().message_subscribe(partner_ids=request.env.user.partner_id.ids) else: partner = request.env['res.partner'].sudo().search([('email', '=', post['email_from'])]) if partner: diff --git a/addons/website_slides/models/slides.py b/addons/website_slides/models/slides.py index d390570d448..410ef170ebb 100644 --- a/addons/website_slides/models/slides.py +++ b/addons/website_slides/models/slides.py @@ -392,7 +392,7 @@ class Slide(models.Model): if not self.user_has_groups('website.group_website_publisher'): values['website_published'] = False slide = super(Slide, self).create(values) - slide.channel_id.message_subscribe_users() + slide.channel_id.message_subscribe(partner_ids=self.env.user.partner_id.ids) slide._post_publication() return slide