diff --git a/mail_follower_custom_notification/README.rst b/mail_follower_custom_notification/README.rst new file mode 100644 index 00000000..406f160d --- /dev/null +++ b/mail_follower_custom_notification/README.rst @@ -0,0 +1,66 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +========================================== +Custom notification settings for followers +========================================== + +In standard Odoo, receiving mail notifications is an all or nothing affair. +This module allows you users to decide per followed record if they want to +receive emails or not. Further, they can choose to receive notification about +their own messages. + +You can also set defaults for this settings on the subtype in question. + +Configuration +============= + +When followers open their subscriptions, they will be offered the choice to +override mail settings and to force being notified about their own messages. + +You can add defaults per message sub type for this settings in Settings / +Technical / Email / Subtypes. Here, you also have the opportunity to apply +those defaults to existing subscriptions. Note that this overrides all +customizations your users already have done. + +Usage +===== + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/205/8.0 + +For further information, please visit: + +* https://www.odoo.com/forum/help-1 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed feedback +`here `_. + +Credits +======= + +Contributors +------------ + +* Holger Brunn + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/mail_follower_custom_notification/__init__.py b/mail_follower_custom_notification/__init__.py new file mode 100644 index 00000000..968fe660 --- /dev/null +++ b/mail_follower_custom_notification/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import models +from . import wizards diff --git a/mail_follower_custom_notification/__openerp__.py b/mail_follower_custom_notification/__openerp__.py new file mode 100644 index 00000000..e15b28ba --- /dev/null +++ b/mail_follower_custom_notification/__openerp__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Custom notification settings for followers", + "version": "8.0.1.0.0", + "author": "Therp BV,Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Social Network", + "summary": "Let followers choose if they want to receive email " + "notifications for a given subscription", + "depends": [ + 'mail', + ], + "data": [ + "wizards/mail_subtype_assign_custom_notifications.xml", + "views/mail_message_subtype.xml", + 'views/templates.xml', + ], + "qweb": [ + 'static/src/xml/mail_follower_custom_notification.xml', + ], + "images": [ + 'images/mail_follower_custom_notification.png', + ], + "installable": True, +} diff --git a/mail_follower_custom_notification/images/mail_follower_custom_notification.png b/mail_follower_custom_notification/images/mail_follower_custom_notification.png new file mode 100644 index 00000000..3c63b9a7 Binary files /dev/null and b/mail_follower_custom_notification/images/mail_follower_custom_notification.png differ diff --git a/mail_follower_custom_notification/models/__init__.py b/mail_follower_custom_notification/models/__init__.py new file mode 100644 index 00000000..b0a8e2ce --- /dev/null +++ b/mail_follower_custom_notification/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import mail_followers +from . import mail_thread +from . import mail_message +from . import mail_notification +from . import mail_message_subtype diff --git a/mail_follower_custom_notification/models/mail_followers.py b/mail_follower_custom_notification/models/mail_followers.py new file mode 100644 index 00000000..82719402 --- /dev/null +++ b/mail_follower_custom_notification/models/mail_followers.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import api, fields, models + + +class MailFollowers(models.Model): + _inherit = 'mail.followers' + + force_mail_subtype_ids = fields.Many2many( + 'mail.message.subtype', 'mail_followers_force_mail_rel', + 'mail_followers_id', 'mail_message_subtype_id', + string='Force mails from subtype') + + force_nomail_subtype_ids = fields.Many2many( + 'mail.message.subtype', 'mail_followers_force_nomail_rel', + 'mail_followers_id', 'mail_message_subtype_id', + string='Force no mails from subtype') + + force_own_subtype_ids = fields.Many2many( + 'mail.message.subtype', 'mail_followers_force_own_rel', + 'mail_followers_id', 'mail_message_subtype_id', + string='Force own mails from subtype') + + @api.model + @api.returns('self', lambda x: x.id) + def create(self, values): + this = super(MailFollowers, self).create(values) + for subtype in this.subtype_ids: + if not subtype.res_model and\ + subtype.custom_notification_model_ids and\ + this.res_model not in\ + subtype.custom_notification_model_ids\ + .mapped('model'): + continue + if subtype.custom_notification_mail == 'force_yes': + this.force_mail_subtype_ids += subtype + if subtype.custom_notification_mail == 'force_no': + this.force_nomail_subtype_ids += subtype + if subtype.custom_notification_own: + this.force_own_subtype_ids += subtype + return this diff --git a/mail_follower_custom_notification/models/mail_message.py b/mail_follower_custom_notification/models/mail_message.py new file mode 100644 index 00000000..6ec41b24 --- /dev/null +++ b/mail_follower_custom_notification/models/mail_message.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import api, models + + +class MailMessage(models.Model): + _inherit = 'mail.message' + + @api.multi + def _notify(self, force_send=False, user_signature=True): + """notify author if she's a follower and turned on force_own""" + self.ensure_one() + if self.subtype_id and self.model and self.res_id: + author_follower = self.env['mail.followers'].search([ + ('res_model', '=', self.model), + ('res_id', '=', self.res_id), + ('partner_id', '=', self.author_id.id), + ('force_own_subtype_ids', '=', self.subtype_id.id), + ]) + self.env['mail.notification']._notify( + self.id, partners_to_notify=author_follower.partner_id.ids, + force_send=force_send, user_signature=user_signature) + return super(MailMessage, self)._notify( + self.id, force_send=force_send, user_signature=user_signature) diff --git a/mail_follower_custom_notification/models/mail_message_subtype.py b/mail_follower_custom_notification/models/mail_message_subtype.py new file mode 100644 index 00000000..f58e199d --- /dev/null +++ b/mail_follower_custom_notification/models/mail_message_subtype.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import fields, models + + +class MailMessageSubtype(models.Model): + _inherit = 'mail.message.subtype' + + custom_notification_mail = fields.Selection( + [('force_yes', 'Force yes'), ('force_no', 'Force no')], + string='Send mail notification', help='Leave empty to use the ' + 'on the partner\'s form, set to "Force yes" to always send messages ' + 'of this type via email, and "Force no" to never send messages of ' + 'type via email') + custom_notification_own = fields.Boolean( + 'Notify about own messages', help='Check this to have notifications ' + 'generated and sent via email about own messages') + custom_notification_model_ids = fields.Many2many( + 'ir.model', string='Models', help='Choose for which models the ' + 'custom configuration applies. This is only necessary if your subtype ' + 'doesn\'t set a model itself', domain=[('osv_memory', '=', False)]) diff --git a/mail_follower_custom_notification/models/mail_notification.py b/mail_follower_custom_notification/models/mail_notification.py new file mode 100644 index 00000000..c70e0f50 --- /dev/null +++ b/mail_follower_custom_notification/models/mail_notification.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import api, models + + +class MailNotification(models.Model): + _inherit = 'mail.notification' + + @api.multi + def get_partners_to_email(self, message): + partner_ids = super(MailNotification, self)\ + .get_partners_to_email(message) + for this in self: + follower = self.env['mail.followers'].search([ + ('res_model', '=', message.model), + ('res_id', '=', message.res_id), + ('partner_id', '=', this.partner_id.id), + '|', '|', + ('force_nomail_subtype_ids', '=', message.subtype_id.id), + ('force_mail_subtype_ids', '=', message.subtype_id.id), + ('force_own_subtype_ids', '=', message.subtype_id.id), + ]) + if not follower: + continue + if (message.subtype_id in follower.force_mail_subtype_ids or + message.subtype_id in follower.force_own_subtype_ids) and\ + this.partner_id.id not in partner_ids: + partner_ids.append(this.partner_id.id) + if message.subtype_id in follower.force_nomail_subtype_ids and\ + this.partner_id.id in partner_ids: + partner_ids.remove(this.partner_id.id) + return partner_ids diff --git a/mail_follower_custom_notification/models/mail_thread.py b/mail_follower_custom_notification/models/mail_thread.py new file mode 100644 index 00000000..910eb222 --- /dev/null +++ b/mail_follower_custom_notification/models/mail_thread.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import SUPERUSER_ID, api, models +from openerp.addons.mail.mail_thread import mail_thread + + +class MailThread(models.Model): + _inherit = 'mail.thread' + + @api.multi + def _get_subscription_data(self, name, args, user_pid=None): + result = super(MailThread, self)._get_subscription_data( + name, args, user_pid=user_pid) + subtypes = self.env['mail.message.subtype'].search([ + ('hidden', '=', False), + '|', + ('res_model', '=', self._name), + ('res_model', '=', False), + ]) + for follower in self.env['mail.followers'].search([ + ('res_model', '=', self._name), + ('res_id', 'in', result.keys()), + ('partner_id', '=', user_pid or self.env.user.partner_id.id), + ]): + # values are ordered dicts, so we get the correct matches + for subtype, data in zip( + subtypes, + result[follower.res_id]['message_subtype_data'].values()): + data['force_mail'] = 'default' + if subtype in follower.force_mail_subtype_ids: + data['force_mail'] = 'force_yes' + elif subtype in follower.force_nomail_subtype_ids: + data['force_mail'] = 'force_no' + data['force_own'] =\ + subtype in follower.force_own_subtype_ids + return result + + @api.multi + def message_custom_notification_update_user(self, custom_notifications): + """change custom_notifications from user ids to partner ids""" + user2partner = dict( + self.env['res.users'].browse(map(int, custom_notifications.keys())) + .mapped(lambda user: (str(user.id), str(user.partner_id.id))) + ) + return self.message_custom_notification_update({ + user2partner[user_id]: data + for user_id, data in custom_notifications.iteritems() + }) + + @api.multi + def message_custom_notification_update(self, custom_notifications): + """custom_notifications is a dictionary with partner ids as keys + and dictionaries mapping message subtype ids to custom notification + values""" + def ids_with_value(data, key, value): + return map(lambda x: int(x[0]), + filter(lambda x: x[1][key] == value, + data.iteritems())) + + custom_notifications = { + int(key): value + for key, value in custom_notifications.iteritems() + if key != 'False' + } + + for follower in self.env['mail.followers'].search([ + ('res_model', '=', self._name), + ('res_id', 'in', self.ids), + ('partner_id', 'in', custom_notifications.keys()), + ]): + data = custom_notifications[follower.partner_id.id] + follower.write({ + 'force_mail_subtype_ids': [(6, 0, ids_with_value( + data, 'force_mail', 'force_yes'))], + 'force_nomail_subtype_ids': [(6, 0, ids_with_value( + data, 'force_mail', 'force_no'))], + 'force_own_subtype_ids': [(6, 0, ids_with_value( + data, 'force_own', '1'))] + }), + + def _register_hook(self, cr): + model_ids = self.pool['ir.model'].search(cr, SUPERUSER_ID, []) + rebuilt = [] + for model in self.pool['ir.model'].browse(cr, SUPERUSER_ID, model_ids): + if model.model not in self.pool: + continue + model_object = self.pool[model.model] + if not isinstance(model_object, mail_thread): + continue + if isinstance(model_object, MailThread): + continue + bases = list(model_object.__class__.__bases__) + if MailThread not in bases: + bases.insert(1, MailThread) + class_dict = dict(model_object.__dict__) + class_dict['_inherit'] = model_object._name + new_model_class = type(model_object._name, tuple(bases), + class_dict) + new_model = new_model_class._build_model(self.pool, cr) + self.pool.models[model.model] = new_model + new_model._prepare_setup(cr, SUPERUSER_ID) + new_model._setup_base(cr, SUPERUSER_ID, False) + new_model._setup_fields(cr, SUPERUSER_ID) + rebuilt.append(new_model) + for model in rebuilt: + model._setup_complete(cr, SUPERUSER_ID) + return super(MailThread, self)._register_hook(cr) diff --git a/mail_follower_custom_notification/static/description/icon.png b/mail_follower_custom_notification/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/mail_follower_custom_notification/static/description/icon.png differ diff --git a/mail_follower_custom_notification/static/src/css/mail_follower_custom_notification.css b/mail_follower_custom_notification/static/src/css/mail_follower_custom_notification.css new file mode 100644 index 00000000..92b9ecb9 --- /dev/null +++ b/mail_follower_custom_notification/static/src/css/mail_follower_custom_notification.css @@ -0,0 +1,5 @@ +.oe_custom_notification legend +{ + font-size: inherit; + margin-bottom: 0px; +} diff --git a/mail_follower_custom_notification/static/src/js/mail_follower_custom_notification.js b/mail_follower_custom_notification/static/src/js/mail_follower_custom_notification.js new file mode 100644 index 00000000..104342b1 --- /dev/null +++ b/mail_follower_custom_notification/static/src/js/mail_follower_custom_notification.js @@ -0,0 +1,79 @@ +//-*- coding: utf-8 -*- +//© 2015 Therp BV +//License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +openerp.mail_follower_custom_notification = function(instance) +{ + instance.mail_followers.Followers.include({ + display_subtypes: function(data, id, dialog) + { + var $list = this.$('.oe_subtype_list ul'); + if (dialog) + { + $list = this.$dialog.$el; + } + $list.empty(); + this._super(data, id, dialog); + $list.find('input[type=checkbox]').change(function() + { + $list.find(_.str.sprintf( + '#custom_notification_%s%s', + jQuery(this).data('id'), + dialog ? '_dialog' : '' + )) + .toggle(jQuery(this).prop('checked')); + }); + if(!dialog) + { + $list.find('.oe_custom_notification input[type=radio]') + .change(this.proxy('do_update_subscription')); + }; + }, + do_update_subscription: function(event, user_pid) + { + /* + if(jQuery(event.currentTarget).parents('.oe_custom_notification') + .length) + { + // mail reacts on all inputs, suppress for our inputs + return jQuery.when(); + } + */ + var self = this, + update_func = 'message_custom_notification_update_user', + follower_ids = [this.session.uid], + custom_notifications = {}, + oe_action = this.$('.oe_actions'); + if(user_pid) + { + update_func = 'message_custom_notification_update'; + follower_ids = [user_pid]; + oe_action = jQuery('.oe_edit_actions'); + } + _(follower_ids).each(function(follower) + { + + var follower_settings = custom_notifications[follower] = {}; + oe_action.find('.oe_custom_notification') + .each(function() + { + var id = parseInt(jQuery(this).data('id')), + settings = follower_settings[id] = {}; + settings['force_mail'] = jQuery(this) + .find('.oe_custom_notification_mail input:checked') + .val(); + settings['force_own'] = jQuery(this) + .find('.oe_custom_notification_own input:checked') + .val(); + }); + }); + return jQuery.when(this._super.apply(this, arguments)) + .then(function() + { + return self.ds_model.call( + update_func, + [[self.view.datarecord.id], custom_notifications]) + }) + }, + }); +} diff --git a/mail_follower_custom_notification/static/src/xml/mail_follower_custom_notification.xml b/mail_follower_custom_notification/static/src/xml/mail_follower_custom_notification.xml new file mode 100644 index 00000000..25fee159 --- /dev/null +++ b/mail_follower_custom_notification/static/src/xml/mail_follower_custom_notification.xml @@ -0,0 +1,38 @@ + + diff --git a/mail_follower_custom_notification/tests/__init__.py b/mail_follower_custom_notification/tests/__init__.py new file mode 100644 index 00000000..ef042795 --- /dev/null +++ b/mail_follower_custom_notification/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import test_mail_follower_custom_notification diff --git a/mail_follower_custom_notification/tests/test_mail_follower_custom_notification.py b/mail_follower_custom_notification/tests/test_mail_follower_custom_notification.py new file mode 100644 index 00000000..11bed947 --- /dev/null +++ b/mail_follower_custom_notification/tests/test_mail_follower_custom_notification.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp.tests.common import TransactionCase + + +class TestMailFollowerCustomNotification(TransactionCase): + def test_mail_follower_custom_notification(self): + self.env['mail.thread']._register_hook() + followed_partner = self.env['res.partner'].create({ + 'name': 'I\'m followed', + }) + demo_user = self.env.ref('base.user_demo') + followed_partner_demo = followed_partner.sudo(demo_user.id) + followed_partner_demo.message_subscribe_users() + + # see if default subscriptions return default custom settings + subscription_data = followed_partner_demo._get_subscription_data( + None, None) + self.assertEqual( + subscription_data[followed_partner.id]['message_subtype_data'] + ['Discussions']['force_mail'], + 'default') + self.assertEqual( + subscription_data[followed_partner.id]['message_subtype_data'] + ['Discussions']['force_own'], + False) + + # set custom settings + mt_comment = self.env.ref('mail.mt_comment') + followed_partner_demo.message_custom_notification_update_user({ + str(demo_user.id): { + str(mt_comment.id): { + 'force_mail': 'force_yes', + 'force_own': '1', + }, + }, + }) + # see if we can read them back + subscription_data = followed_partner_demo._get_subscription_data( + None, None) + self.assertEqual( + subscription_data[followed_partner.id]['message_subtype_data'] + ['Discussions']['force_mail'], + 'force_yes') + self.assertEqual( + subscription_data[followed_partner.id]['message_subtype_data'] + ['Discussions']['force_own'], + True) + + # post a message and see if we successfully forced a notification to + # ourselves + followed_partner_demo.message_post('hello world', subtype='mt_comment') + self.assertEqual( + followed_partner_demo.message_ids[:-1].notification_ids.partner_id, + demo_user.partner_id) + + # assign default values on message subtype and apply them to all + # followers + mt_comment.custom_notification_model_ids = self.env['ir.model']\ + .search([('model', '=', 'res.partner')]) + wizard = self.env['mail.subtype.assign.custom.notifications']\ + .with_context(active_ids=mt_comment.ids)\ + .create({}) + wizard.button_apply() + subscription_data = followed_partner_demo._get_subscription_data( + None, None) + self.assertEqual( + subscription_data[followed_partner.id]['message_subtype_data'] + ['Discussions']['force_mail'], + 'default') + self.assertEqual( + subscription_data[followed_partner.id]['message_subtype_data'] + ['Discussions']['force_own'], + False) diff --git a/mail_follower_custom_notification/views/mail_message_subtype.xml b/mail_follower_custom_notification/views/mail_message_subtype.xml new file mode 100644 index 00000000..0d60986c --- /dev/null +++ b/mail_follower_custom_notification/views/mail_message_subtype.xml @@ -0,0 +1,18 @@ + + + + + mail.message.subtype + + + + + + + + + + + + + diff --git a/mail_follower_custom_notification/views/templates.xml b/mail_follower_custom_notification/views/templates.xml new file mode 100644 index 00000000..fbfbec8b --- /dev/null +++ b/mail_follower_custom_notification/views/templates.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/mail_follower_custom_notification/wizards/__init__.py b/mail_follower_custom_notification/wizards/__init__.py new file mode 100644 index 00000000..e968c053 --- /dev/null +++ b/mail_follower_custom_notification/wizards/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import mail_subtype_assign_custom_notifications diff --git a/mail_follower_custom_notification/wizards/mail_subtype_assign_custom_notifications.py b/mail_follower_custom_notification/wizards/mail_subtype_assign_custom_notifications.py new file mode 100644 index 00000000..c0413e7e --- /dev/null +++ b/mail_follower_custom_notification/wizards/mail_subtype_assign_custom_notifications.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# © 2015 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import api, fields, models + + +class MailSubtypeAssignCustomNotifications(models.TransientModel): + _name = 'mail.subtype.assign.custom.notifications' + _description = 'Assign custom notification settings to existing followers' + + subtype_ids = fields.Many2many( + 'mail.message.subtype', 'mail_subtype_assign_custom_notifications_rel', + string='Subtypes', required=True, + default=lambda self: [(6, 0, self.env.context.get('active_ids', []))]) + + @api.multi + def button_apply(self): + self.ensure_one() + for subtype in self.subtype_ids: + domain = [('subtype_ids', '=', subtype.id)] + if subtype.custom_notification_model_ids: + domain.append( + ('res_model', 'in', + subtype.custom_notification_model_ids.mapped('model'))) + self.env['mail.followers'].with_context(active_test=False)\ + .search(domain)\ + .write({ + 'force_mail_subtype_ids': [ + (4, subtype.id) + if subtype.custom_notification_mail == 'force_yes' + else + (3, subtype.id) + ], + 'force_nomail_subtype_ids': [ + (4, subtype.id) + if subtype.custom_notification_mail == 'force_no' + else + (3, subtype.id) + ], + 'force_own_subtype_ids': [ + (4, subtype.id) + if subtype.custom_notification_own + else + (3, subtype.id) + ], + }) diff --git a/mail_follower_custom_notification/wizards/mail_subtype_assign_custom_notifications.xml b/mail_follower_custom_notification/wizards/mail_subtype_assign_custom_notifications.xml new file mode 100644 index 00000000..adbab627 --- /dev/null +++ b/mail_follower_custom_notification/wizards/mail_subtype_assign_custom_notifications.xml @@ -0,0 +1,30 @@ + + + + + mail.subtype.assign.custom.notifications + +
+ + + +
+
+
+
+
+ +
+