1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2009-today OpenERP SA (<http://www.openerp.com>)
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
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
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/>
20 ##############################################################################
21 from openerp.osv import osv, fields
22 from openerp import tools, SUPERUSER_ID
23 from openerp.tools.translate import _
24 from openerp.tools.mail import plaintext2html
26 class mail_followers(osv.Model):
27 """ mail_followers holds the data related to the follow mechanism inside
28 OpenERP. Partners can choose to follow documents (records) of any kind
29 that inherits from mail.thread. Following documents allow to receive
30 notifications for new messages.
31 A subscription is characterized by:
32 :param: res_model: model of the followed objects
33 :param: res_id: ID of resource (may be 0 for every objects)
35 _name = 'mail.followers'
36 _rec_name = 'partner_id'
38 _description = 'Document Followers'
40 'res_model': fields.char('Related Document Model', size=128,
41 required=True, select=1,
42 help='Model of the followed resource'),
43 'res_id': fields.integer('Related Document ID', select=1,
44 help='Id of the followed resource'),
45 'partner_id': fields.many2one('res.partner', string='Related Partner',
46 ondelete='cascade', required=True, select=1),
47 'subtype_ids': fields.many2many('mail.message.subtype', string='Subtype',
48 help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall."),
51 _sql_constraints = [('mail_followers_res_partner_res_model_id_uniq','unique(res_model,res_id,partner_id)','Error, a partner cannot follow twice the same object.')]
53 class mail_notification(osv.Model):
54 """ Class holding notifications pushed to partners. Followers and partners
55 added in 'contacts to notify' receive notifications. """
56 _name = 'mail.notification'
57 _rec_name = 'partner_id'
59 _description = 'Notifications'
62 'partner_id': fields.many2one('res.partner', string='Contact',
63 ondelete='cascade', required=True, select=1),
64 'read': fields.boolean('Read', select=1),
65 'starred': fields.boolean('Starred', select=1,
66 help='Starred message that goes into the todo mailbox'),
67 'message_id': fields.many2one('mail.message', string='Message',
68 ondelete='cascade', required=True, select=1),
77 cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_starred_message_id',))
79 cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, read, starred, message_id)')
81 def get_partners_to_email(self, cr, uid, ids, message, context=None):
82 """ Return the list of partners to notify, based on their preferences.
84 :param browse_record message: mail.message to notify
85 :param list partners_to_notify: optional list of partner ids restricting
86 the notifications to process
89 for notification in self.browse(cr, uid, ids, context=context):
92 partner = notification.partner_id
93 # Do not send to partners without email address defined
96 # Do not send to partners having same email address than the author (can cause loops or bounce effect due to messy database)
97 if message.author_id and message.author_id.email == partner.email:
99 # Partner does not want to receive any emails or is opt-out
100 if partner.notify_email == 'none':
102 notify_pids.append(partner.id)
105 def get_signature_footer(self, cr, uid, user_id, res_model=None, res_id=None, context=None):
106 """ Format a standard footer for notification emails (such as pushed messages
107 notification or invite emails).
113 <small>Sent from <a ...>Your Company</a> using <a ...>OpenERP</a>.</small>
121 user = self.pool.get("res.users").browse(cr, SUPERUSER_ID, [user_id], context=context)[0]
123 signature = plaintext2html(user.signature)
125 signature = "--<br />%s" % user.name
126 footer = tools.append_content_to_html(footer, signature, plaintext=False, container_tag='p')
128 # add company signature
129 if user.company_id.website:
130 website_url = ('http://%s' % user.company_id.website) if not user.company_id.website.lower().startswith(('http:', 'https:')) \
131 else user.company_id.website
132 company = "<a style='color:inherit' href='%s'>%s</a>" % (website_url, user.company_id.name)
134 company = user.company_id.name
135 sent_by = _('Sent by %(company)s using %(odoo)s.')
137 signature_company = '<small>%s</small>' % (sent_by % {
139 'odoo': "<a style='color:inherit' href='https://www.odoo.com/'>Odoo</a>"
141 footer = tools.append_content_to_html(footer, signature_company, plaintext=False, container_tag='div')
145 def update_message_notification(self, cr, uid, ids, message_id, partner_ids, context=None):
146 existing_pids = set()
150 for notification in self.browse(cr, uid, ids, context=context):
151 existing_pids.add(notification.partner_id.id)
153 # update existing notifications
154 self.write(cr, uid, ids, {'read': False}, context=context)
156 # create new notifications
157 new_pids = set(partner_ids) - existing_pids
158 for new_pid in new_pids:
159 new_notif_ids.append(self.create(cr, uid, {'message_id': message_id, 'partner_id': new_pid, 'read': False}, context=context))
162 def _notify_email(self, cr, uid, ids, message_id, force_send=False, user_signature=True, context=None):
163 message = self.pool['mail.message'].browse(cr, SUPERUSER_ID, message_id, context=context)
166 email_pids = self.get_partners_to_email(cr, uid, ids, message, context=None)
170 # compute email body (signature, company data)
171 body_html = message.body
172 # add user signature except for mail groups, where users are usually adding their own signatures already
173 if user_signature and message.model != 'mail.group':
174 user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None
175 signature_company = self.get_signature_footer(cr, uid, user_id, res_model=message.model, res_id=message.res_id, context=context)
176 body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')
178 # compute email references
179 references = message.parent_id.message_id if message.parent_id else False
181 # create email values
183 chunks = [email_pids[x:x + max_recipients] for x in xrange(0, len(email_pids), max_recipients)]
187 'mail_message_id': message.id,
189 'body_html': body_html,
190 'recipient_ids': [(4, id) for id in chunk],
191 'references': references,
193 email_ids.append(self.pool.get('mail.mail').create(cr, uid, mail_values, context=context))
194 if force_send and len(chunks) < 2: # for more than 50 followers, use the queue system
195 self.pool.get('mail.mail').send(cr, uid, email_ids, context=context)
198 def _notify(self, cr, uid, message_id, partners_to_notify=None, context=None,
199 force_send=False, user_signature=True):
200 """ Send by email the notification depending on the user preferences
202 :param list partners_to_notify: optional list of partner ids restricting
203 the notifications to process
204 :param bool force_send: if True, the generated mail.mail is
205 immediately sent after being created, as if the scheduler
206 was executed for this message only.
207 :param bool user_signature: if True, the generated mail.mail body is
208 the body of the related mail.message with the author's signature
210 notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', message_id), ('partner_id', 'in', partners_to_notify)], context=context)
212 # update or create notifications
213 new_notif_ids = self.update_message_notification(cr, SUPERUSER_ID, notif_ids, message_id, partners_to_notify, context=context)
215 # mail_notify_noemail (do not send email) or no partner_ids: do not send, return
216 if context and context.get('mail_notify_noemail'):
219 # browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
220 self._notify_email(cr, SUPERUSER_ID, new_notif_ids, message_id, force_send, user_signature, context=context)