diff --git a/addons/mail/models/ir_model.py b/addons/mail/models/ir_model.py index 01862352e96..5ab47dc0fc0 100644 --- a/addons/mail/models/ir_model.py +++ b/addons/mail/models/ir_model.py @@ -46,6 +46,12 @@ class IrModel(models.Model): model_class._inherit = parents + ['mail.thread'] return model_class + def unlink(self): + # Delete followers for models that will be unlinked. + query = "DELETE FROM mail_followers WHERE res_model IN %s" + self.env.cr.execute(query, [tuple(self.mapped('model'))]) + return super(IrModel, self).unlink() + class IrModelField(models.Model): _inherit = 'ir.model.fields' diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py index 07ff6ea8c74..281e6f1c5b4 100644 --- a/addons/mail/models/mail_followers.py +++ b/addons/mail/models/mail_followers.py @@ -18,12 +18,11 @@ class Followers(models.Model): _log_access = False _description = 'Document Followers' - res_model_id = fields.Many2one( - 'ir.model', 'Related Document Model', - index=True, required=True, ondelete='cascade', - help='Model of the followed resource') + # Note. There is no integrity check on model names for performance reasons. + # However, followers of unlinked models are deleted by models themselves + # (see 'ir.model' inheritance). res_model = fields.Char( - 'Related Document Model Name', index=True, readonly=True, related='res_model_id.model', compute_sudo=True, store=True) + 'Related Document Model Name', required=True, index=True) res_id = fields.Integer( 'Related Document ID', index=True, help='Id of the followed resource') partner_id = fields.Many2one( @@ -40,7 +39,6 @@ class Followers(models.Model): :param force: if True, delete existing followers before creating new one using the subtypes given in the parameters """ - res_model_id = self.env['ir.model']._get(res_model).id force_mode = force or (all(partner_data.values()) and all(channel_data.values())) generic = [] specific = {} @@ -50,7 +48,7 @@ class Followers(models.Model): followers = self.sudo().search([ '&', - '&', ('res_model_id', '=', res_model_id), ('res_id', 'in', res_ids), + '&', ('res_model', '=', res_model), ('res_id', 'in', res_ids), '|', ('partner_id', 'in', list(partner_data)), ('channel_id', 'in', list(channel_data))]) if force_mode: @@ -82,9 +80,9 @@ class Followers(models.Model): 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_id': res_model_id, 'partner_id': pid, 'subtype_ids': [(6, 0, partner_data.get(pid) or default_subtypes.ids)]}]) + 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_id': res_model_id, 'channel_id': cid, 'subtype_ids': [(6, 0, channel_data.get(cid) or default_subtypes.ids)]}]) + 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: @@ -98,13 +96,13 @@ class Followers(models.Model): # subscribe new followers for new_pid in new_pids: command.append((0, 0, { - 'res_model_id': res_model_id, + '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_id': res_model_id, + 'res_model': res_model, 'channel_id': new_cid, 'subtype_ids': [(6, 0, channel_data.get(new_cid) or default_subtypes.ids)], })) diff --git a/addons/mail/tests/test_mail_followers.py b/addons/mail/tests/test_mail_followers.py index 44c9cc3e47a..1887943de4e 100644 --- a/addons/mail/tests/test_mail_followers.py +++ b/addons/mail/tests/test_mail_followers.py @@ -26,13 +26,12 @@ class TestMailFollowers(TestMail): 'mail.test', groups.ids, {self.user_employee.partner_id.id: [self.mt_mg_nodef.id]}, {test_channel.id: [self.mt_al_nodef.id]}) - mail_channel_model_id = self.env['ir.model']._get('mail.test').id self.assertFalse(specific) self.assertEqual(len(generic), 2) items = [it[2] for it in generic] - self.assertEqual({item['res_model_id'] for item in items}, - {mail_channel_model_id}) + self.assertEqual({item['res_model'] for item in items}, + {'mail.test'}) self.assertEqual({item['channel_id'] for item in items if item.get('channel_id')}, {test_channel.id}) self.assertEqual({item['partner_id'] for item in items if item.get('partner_id')}, @@ -42,9 +41,8 @@ class TestMailFollowers(TestMail): def test_m2o_command_update_selective(self): test_channel = self.env['mail.channel'].create({'name': 'Test'}) - mail_channel_model_id = self.env['ir.model']._get('mail.test').id groups = self.test_pigs | self.test_public - self.env['mail.followers'].create({'partner_id': self.user_employee.partner_id.id, 'res_model_id': mail_channel_model_id, 'res_id': self.test_pigs.id}) + self.env['mail.followers'].create({'partner_id': self.user_employee.partner_id.id, 'res_model': 'mail.test', 'res_id': self.test_pigs.id}) generic, specific = self.env['mail.followers']._add_follower_command( 'mail.test', groups.ids, {self.user_employee.partner_id.id: [self.mt_mg_nodef.id]}, @@ -53,12 +51,12 @@ class TestMailFollowers(TestMail): self.assertEqual(len(generic), 1) self.assertEqual(len(specific), 1) - self.assertEqual(generic[0][2]['res_model_id'], mail_channel_model_id) + self.assertEqual(generic[0][2]['res_model'], 'mail.test') self.assertEqual(generic[0][2]['channel_id'], test_channel.id) self.assertEqual(set(generic[0][2]['subtype_ids'][0][2]), set(self.default_group_subtypes.ids)) self.assertEqual(list(specific), [self.test_public.id]) - self.assertEqual(specific[self.test_public.id][0][2]['res_model_id'], mail_channel_model_id) + self.assertEqual(specific[self.test_public.id][0][2]['res_model'], 'mail.test') self.assertEqual(specific[self.test_public.id][0][2]['partner_id'], self.user_employee.partner_id.id) self.assertEqual(set(specific[self.test_public.id][0][2]['subtype_ids'][0][2]), set([self.mt_mg_nodef.id])) @@ -139,7 +137,7 @@ class TestMailFollowers(TestMail): test_channel = self.env['mail.channel'].create({'name': 'Follower Channel'}) with self.assertRaises(IntegrityError), mute_logger('odoo.sql_db'): self.env['mail.followers'].create({ - 'res_model_id': self.env['ir.model']._get('mail.test').id, + 'res_model': test_record._name, 'res_id': test_record.id, 'partner_id': self.user_employee.partner_id.id, 'channel_id': test_channel.id, diff --git a/addons/mail/views/mail_followers_views.xml b/addons/mail/views/mail_followers_views.xml index adbea78d7f5..fa5cfbf0116 100644 --- a/addons/mail/views/mail_followers_views.xml +++ b/addons/mail/views/mail_followers_views.xml @@ -9,7 +9,7 @@ 10 - + @@ -24,7 +24,7 @@ - + diff --git a/odoo/addons/test_performance/tests/test_performance.py b/odoo/addons/test_performance/tests/test_performance.py index 6d9e179c299..1233bfcbc28 100644 --- a/odoo/addons/test_performance/tests/test_performance.py +++ b/odoo/addons/test_performance/tests/test_performance.py @@ -180,7 +180,7 @@ class TestPerformance(TransactionCase): model = self.env['test_performance.mail'] model.with_context(tracking_disable=True).create({'name': self.str('X')}) - @queryCount(admin=39, demo=55) + @queryCount(admin=38, demo=54) def test_create_mail_with_tracking(self): """ Create records inheriting from 'mail.thread' (with field tracking). """ model = self.env['test_performance.mail']