[FIX] mail: handle opt_out parameter. Please see comment in code for more info.
[odoo/odoo.git] / addons / mail / mail_followers.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2009-today OpenERP SA (<http://www.openerp.com>)
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>
19 #
20 ##############################################################################
21 from openerp.osv import osv, fields
22 from openerp import tools, SUPERUSER_ID
23
24 class mail_followers(osv.Model):
25     """ mail_followers holds the data related to the follow mechanism inside
26         OpenERP. Partners can choose to follow documents (records) of any kind
27         that inherits from mail.thread. Following documents allow to receive
28         notifications for new messages.
29         A subscription is characterized by:
30             :param: res_model: model of the followed objects
31             :param: res_id: ID of resource (may be 0 for every objects)
32     """
33     _name = 'mail.followers'
34     _rec_name = 'partner_id'
35     _log_access = False
36     _description = 'Document Followers'
37     _columns = {
38         'res_model': fields.char('Related Document Model', size=128,
39                         required=True, select=1,
40                         help='Model of the followed resource'),
41         'res_id': fields.integer('Related Document ID', select=1,
42                         help='Id of the followed resource'),
43         'partner_id': fields.many2one('res.partner', string='Related Partner',
44                         ondelete='cascade', required=True, select=1),
45         'subtype_ids': fields.many2many('mail.message.subtype', string='Subtype',
46             help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall."),
47     }
48
49
50 class mail_notification(osv.Model):
51     """ Class holding notifications pushed to partners. Followers and partners
52         added in 'contacts to notify' receive notifications. """
53     _name = 'mail.notification'
54     _rec_name = 'partner_id'
55     _log_access = False
56     _description = 'Notifications'
57
58     _columns = {
59         'partner_id': fields.many2one('res.partner', string='Contact',
60                         ondelete='cascade', required=True, select=1),
61         'read': fields.boolean('Read', select=1),
62         'starred': fields.boolean('Starred', select=1,
63             help='Starred message that goes into the todo mailbox'),
64         'message_id': fields.many2one('mail.message', string='Message',
65                         ondelete='cascade', required=True, select=1),
66     }
67
68     _defaults = {
69         'read': False,
70         'starred': False,
71     }
72
73     def init(self, cr):
74         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_starred_message_id',))
75         if not cr.fetchone():
76             cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, read, starred, message_id)')
77
78     def get_partners_to_notify(self, cr, uid, message, partners_to_notify=None, context=None):
79         """ Return the list of partners to notify, based on their preferences.
80
81             :param browse_record message: mail.message to notify
82             :param list partners_to_notify: optional list of partner ids restricting
83                 the notifications to process
84         """
85         # TDE FIXME HACK: as notification_email_send is not present on the partner
86         # form view, and as opt_out can be used once CRM is installed, we have to
87         # perform this ugly columns check to use the parameter
88         # Please remove me in 8.0 (hint: remove opt_out -> notification to 'never')
89         has_opt_out = self.pool.get('res.partner')._all_columns.get('opt_out')
90         notify_pids = []
91         for notification in message.notification_ids:
92             if notification.read:
93                 continue
94             partner = notification.partner_id
95             # If partners_to_notify specified: restrict to them
96             if partners_to_notify and partner.id not in partners_to_notify:
97                 continue
98             # Do not send to partners without email address defined
99             if not partner.email:
100                 continue
101             # Partner does not want to receive any emails or is opt-out
102             if partner.notification_email_send == 'none' or (has_opt_out and partner.opt_out):
103                 continue
104             # Partner wants to receive only emails and comments
105             if partner.notification_email_send == 'comment' and message.type not in ('email', 'comment'):
106                 continue
107             # Partner wants to receive only emails
108             if partner.notification_email_send == 'email' and message.type != 'email':
109                 continue
110             notify_pids.append(partner.id)
111         return notify_pids
112
113     def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None):
114         """ Send by email the notification depending on the user preferences
115
116             :param list partners_to_notify: optional list of partner ids restricting
117                 the notifications to process
118         """
119         if context is None:
120             context = {}
121         mail_message_obj = self.pool.get('mail.message')
122
123         # optional list of partners to notify: subscribe them if not already done or update the notification
124         if partners_to_notify:
125             notifications_to_update = []
126             notified_partners = []
127             notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', msg_id), ('partner_id', 'in', partners_to_notify)], context=context)
128             for notification in self.browse(cr, SUPERUSER_ID, notif_ids, context=context):
129                 notified_partners.append(notification.partner_id.id)
130                 notifications_to_update.append(notification.id)
131             partners_to_notify = filter(lambda item: item not in notified_partners, partners_to_notify)
132             if notifications_to_update:
133                 self.write(cr, SUPERUSER_ID, notifications_to_update, {'read': False}, context=context)
134             mail_message_obj.write(cr, uid, msg_id, {'notified_partner_ids': [(4, id) for id in partners_to_notify]}, context=context)
135
136         # mail_notify_noemail (do not send email) or no partner_ids: do not send, return
137         if context.get('mail_notify_noemail'):
138             return True
139         # browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
140         msg = self.pool.get('mail.message').browse(cr, SUPERUSER_ID, msg_id, context=context)
141         notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, partners_to_notify=partners_to_notify, context=context)
142         if not notify_partner_ids:
143             return True
144
145         # add the context in the email
146         # TDE FIXME: commented, to be improved in a future branch
147         # quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
148
149         mail_mail = self.pool.get('mail.mail')
150         # add signature
151         body_html = msg.body
152         # if quote_context:
153             # body_html = tools.append_content_to_html(body_html, quote_context, plaintext=False)
154         signature = msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0].signature or ''
155         if signature:
156             body_html = tools.append_content_to_html(body_html, signature, plaintext=True, container_tag='div')
157
158         # email_from: partner-user alias or partner email or mail.message email_from
159         if msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0].alias_domain and msg.author_id.user_ids[0].alias_name:
160             email_from = '%s <%s@%s>' % (msg.author_id.name, msg.author_id.user_ids[0].alias_name, msg.author_id.user_ids[0].alias_domain)
161         elif msg.author_id:
162             email_from = '%s <%s>' % (msg.author_id.name, msg.author_id.email)
163         else:
164             email_from = msg.email_from
165
166         mail_values = {
167             'mail_message_id': msg.id,
168             'auto_delete': True,
169             'body_html': body_html,
170             'email_from': email_from,
171         }
172         email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
173         try:
174             return mail_mail.send(cr, uid, [email_notif_id], recipient_ids=notify_partner_ids, context=context)
175         except Exception:
176             return False