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',
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."),
52
# Modifying followers change access rights to individual documents. As the
53
# cache may contain accessible/inaccessible data, one has to refresh it.
55
def create(self, cr, uid, vals, context=None):
56
res = super(mail_followers, self).create(cr, uid, vals, context=context)
57
self.invalidate_cache(cr, uid, context=context)
60
def write(self, cr, uid, ids, vals, context=None):
61
res = super(mail_followers, self).write(cr, uid, ids, vals, context=context)
62
self.invalidate_cache(cr, uid, context=context)
65
def unlink(self, cr, uid, ids, context=None):
66
res = super(mail_followers, self).unlink(cr, uid, ids, context=context)
67
self.invalidate_cache(cr, uid, context=context)
70
_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.')]
72
class mail_notification(osv.Model):
73
""" Class holding notifications pushed to partners. Followers and partners
74
added in 'contacts to notify' receive notifications. """
75
_name = 'mail.notification'
76
_rec_name = 'partner_id'
78
_description = 'Notifications'
81
'partner_id': fields.many2one('res.partner', string='Contact',
82
ondelete='cascade', required=True, select=1),
83
'is_read': fields.boolean('Read', select=1, oldname='read'),
84
'starred': fields.boolean('Starred', select=1,
85
help='Starred message that goes into the todo mailbox'),
86
'message_id': fields.many2one('mail.message', string='Message',
87
ondelete='cascade', required=True, select=1),
96
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_starred_message_id',))
98
cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, is_read, starred, message_id)')
100
def get_partners_to_email(self, cr, uid, ids, message, context=None):
101
""" Return the list of partners to notify, based on their preferences.
103
:param browse_record message: mail.message to notify
104
:param list partners_to_notify: optional list of partner ids restricting
105
the notifications to process
108
for notification in self.browse(cr, uid, ids, context=context):
109
if notification.is_read:
111
partner = notification.partner_id
112
# Do not send to partners without email address defined
113
if not partner.email:
115
# Do not send to partners having same email address than the author (can cause loops or bounce effect due to messy database)
116
if message.author_id and message.author_id.email == partner.email:
118
# Partner does not want to receive any emails or is opt-out
119
if partner.notify_email == 'none':
121
notify_pids.append(partner.id)
124
def get_signature_footer(self, cr, uid, user_id, res_model=None, res_id=None, context=None, user_signature=True):
125
""" Format a standard footer for notification emails (such as pushed messages
126
notification or invite emails).
132
<small>Sent from <a ...>Your Company</a> using <a ...>OpenERP</a>.</small>
140
user = self.pool.get("res.users").browse(cr, SUPERUSER_ID, [user_id], context=context)[0]
143
signature = user.signature
145
signature = "--<br />%s" % user.name
146
footer = tools.append_content_to_html(footer, signature, plaintext=False)
148
# add company signature
149
if user.company_id.website:
150
website_url = ('http://%s' % user.company_id.website) if not user.company_id.website.lower().startswith(('http:', 'https:')) \
151
else user.company_id.website
152
company = "<a style='color:inherit' href='%s'>%s</a>" % (website_url, user.company_id.name)
154
company = user.company_id.name
155
sent_by = _('Sent by %(company)s using %(odoo)s')
157
signature_company = '<br /><small>%s</small>' % (sent_by % {
159
'odoo': "<a style='color:inherit' href='https://www.odoo.com/'>Odoo</a>"
161
footer = tools.append_content_to_html(footer, signature_company, plaintext=False, container_tag='div')
165
def update_message_notification(self, cr, uid, ids, message_id, partner_ids, context=None):
166
existing_pids = set()
170
for notification in self.browse(cr, uid, ids, context=context):
171
existing_pids.add(notification.partner_id.id)
173
# update existing notifications
174
self.write(cr, uid, ids, {'is_read': False}, context=context)
176
# create new notifications
177
new_pids = set(partner_ids) - existing_pids
178
for new_pid in new_pids:
179
new_notif_ids.append(self.create(cr, uid, {'message_id': message_id, 'partner_id': new_pid, 'is_read': False}, context=context))
182
def _notify_email(self, cr, uid, ids, message_id, force_send=False, user_signature=True, context=None):
183
message = self.pool['mail.message'].browse(cr, SUPERUSER_ID, message_id, context=context)
186
email_pids = self.get_partners_to_email(cr, uid, ids, message, context=None)
190
# compute email body (signature, company data)
191
body_html = message.body
192
# add user signature except for mail groups, where users are usually adding their own signatures already
193
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
194
signature_company = self.get_signature_footer(cr, uid, user_id, res_model=message.model, res_id=message.res_id, context=context, user_signature=(user_signature and message.model != 'mail.group'))
195
if signature_company:
196
body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')
198
# compute email references
199
references = message.parent_id.message_id if message.parent_id else False
202
custom_values = dict()
203
if message.model and message.res_id and self.pool.get(message.model) and hasattr(self.pool[message.model], 'message_get_email_values'):
204
custom_values = self.pool[message.model].message_get_email_values(cr, uid, message.res_id, message, context=context)
206
# create email values
208
chunks = [email_pids[x:x + max_recipients] for x in xrange(0, len(email_pids), max_recipients)]
212
'mail_message_id': message.id,
214
'body_html': body_html,
215
'recipient_ids': [(4, id) for id in chunk],
216
'references': references,
218
mail_values.update(custom_values)
219
email_ids.append(self.pool.get('mail.mail').create(cr, uid, mail_values, context=context))
220
if force_send and len(chunks) < 2: # for more than 50 followers, use the queue system
221
self.pool.get('mail.mail').send(cr, uid, email_ids, context=context)
224
def _notify(self, cr, uid, message_id, partners_to_notify=None, context=None,
225
force_send=False, user_signature=True):
226
""" Send by email the notification depending on the user preferences
228
:param list partners_to_notify: optional list of partner ids restricting
229
the notifications to process
230
:param bool force_send: if True, the generated mail.mail is
231
immediately sent after being created, as if the scheduler
232
was executed for this message only.
233
:param bool user_signature: if True, the generated mail.mail body is
234
the body of the related mail.message with the author's signature
236
notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', message_id), ('partner_id', 'in', partners_to_notify)], context=context)
238
# update or create notifications
239
new_notif_ids = self.update_message_notification(cr, SUPERUSER_ID, notif_ids, message_id, partners_to_notify, context=context)
241
# mail_notify_noemail (do not send email) or no partner_ids: do not send, return
242
if context and context.get('mail_notify_noemail'):
245
# browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
246
self._notify_email(cr, SUPERUSER_ID, new_notif_ids, message_id, force_send, user_signature, context=context)