2012-02-01 16:49:02 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
2012-03-13 15:24:52 +00:00
# Copyright (C) 2009-today OpenERP SA (<http://www.openerp.com>)
2012-02-01 16:49:02 +00:00
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
2015-01-16 10:57:23 +00:00
import threading
2012-12-11 14:33:01 +00:00
from openerp . osv import osv , fields
2012-12-17 14:43:06 +00:00
from openerp import tools , SUPERUSER_ID
2013-01-24 09:42:41 +00:00
from openerp . tools . translate import _
from openerp . tools . mail import plaintext2html
2012-09-13 15:48:44 +00:00
2012-08-13 19:10:06 +00:00
class mail_followers ( osv . Model ) :
""" mail_followers holds the data related to the follow mechanism inside
2012-08-28 11:31:28 +00:00
OpenERP . Partners can choose to follow documents ( records ) of any kind
that inherits from mail . thread . Following documents allow to receive
notifications for new messages .
A subscription is characterized by :
: param : res_model : model of the followed objects
: param : res_id : ID of resource ( may be 0 for every objects )
2012-02-01 16:49:02 +00:00
"""
2012-08-13 19:10:06 +00:00
_name = ' mail.followers '
2012-08-16 09:02:43 +00:00
_rec_name = ' partner_id '
2012-08-09 14:43:45 +00:00
_log_access = False
2012-08-16 09:02:43 +00:00
_description = ' Document Followers '
2012-02-01 16:49:02 +00:00
_columns = {
2014-05-21 09:52:05 +00:00
' res_model ' : fields . char ( ' Related Document Model ' ,
2012-04-02 12:59:26 +00:00
required = True , select = 1 ,
help = ' Model of the followed resource ' ) ,
' res_id ' : fields . integer ( ' Related Document ID ' , select = 1 ,
help = ' Id of the followed resource ' ) ,
2012-08-16 09:02:43 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , string = ' Related Partner ' ,
2012-02-10 12:42:53 +00:00
ondelete = ' cascade ' , required = True , select = 1 ) ,
2012-09-20 10:17:04 +00:00
' subtype_ids ' : fields . many2many ( ' mail.message.subtype ' , string = ' Subtype ' ,
help = " Message subtypes followed, meaning subtypes that will be pushed onto the user ' s Wall. " ) ,
2012-02-01 16:49:02 +00:00
}
2012-02-01 17:05:05 +00:00
2014-07-06 14:44:26 +00:00
#
# Modifying followers change access rights to individual documents. As the
# cache may contain accessible/inaccessible data, one has to refresh it.
#
def create ( self , cr , uid , vals , context = None ) :
res = super ( mail_followers , self ) . create ( cr , uid , vals , context = context )
self . invalidate_cache ( cr , uid , context = context )
return res
def write ( self , cr , uid , ids , vals , context = None ) :
res = super ( mail_followers , self ) . write ( cr , uid , ids , vals , context = context )
self . invalidate_cache ( cr , uid , context = context )
return res
def unlink ( self , cr , uid , ids , context = None ) :
res = super ( mail_followers , self ) . unlink ( cr , uid , ids , context = context )
self . invalidate_cache ( cr , uid , context = context )
return res
2014-08-07 14:51:46 +00:00
_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. ' ) ]
2012-08-17 11:19:36 +00:00
2012-08-09 14:43:45 +00:00
class mail_notification ( osv . Model ) :
2012-08-22 17:23:46 +00:00
""" Class holding notifications pushed to partners. Followers and partners
2012-08-28 11:31:28 +00:00
added in ' contacts to notify ' receive notifications . """
2012-02-01 17:05:05 +00:00
_name = ' mail.notification '
2012-08-16 09:02:43 +00:00
_rec_name = ' partner_id '
2012-02-06 09:06:18 +00:00
_log_access = False
2012-08-16 09:02:43 +00:00
_description = ' Notifications '
2012-08-22 17:23:46 +00:00
2012-02-01 17:05:05 +00:00
_columns = {
2012-08-15 18:44:03 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , string = ' Contact ' ,
2012-11-12 13:17:59 +00:00
ondelete = ' cascade ' , required = True , select = 1 ) ,
2014-08-19 13:35:25 +00:00
' is_read ' : fields . boolean ( ' Read ' , select = 1 , oldname = ' read ' ) ,
2012-11-29 11:30:25 +00:00
' starred ' : fields . boolean ( ' Starred ' , select = 1 ,
help = ' Starred message that goes into the todo mailbox ' ) ,
2012-08-31 09:01:20 +00:00
' message_id ' : fields . many2one ( ' mail.message ' , string = ' Message ' ,
2012-11-12 13:17:59 +00:00
ondelete = ' cascade ' , required = True , select = 1 ) ,
2012-02-01 17:05:05 +00:00
}
2012-08-22 17:23:46 +00:00
2012-02-01 17:05:05 +00:00
_defaults = {
2014-07-06 14:44:26 +00:00
' is_read ' : False ,
2012-11-29 11:30:25 +00:00
' starred ' : False ,
2012-02-01 17:05:05 +00:00
}
2012-08-28 11:31:28 +00:00
2012-08-31 11:52:02 +00:00
def init ( self , cr ) :
2012-12-05 14:06:07 +00:00
cr . execute ( ' SELECT indexname FROM pg_indexes WHERE indexname = %s ' , ( ' mail_notification_partner_id_read_starred_message_id ' , ) )
2012-08-31 11:52:02 +00:00
if not cr . fetchone ( ) :
2014-07-06 14:44:26 +00:00
cr . execute ( ' CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, is_read, starred, message_id) ' )
2012-08-31 11:52:02 +00:00
2013-08-27 15:38:40 +00:00
def get_partners_to_email ( self , cr , uid , ids , message , context = None ) :
2012-09-12 13:38:43 +00:00
""" Return the list of partners to notify, based on their preferences.
2012-09-13 15:48:44 +00:00
: param browse_record message : mail . message to notify
2013-02-26 10:57:55 +00:00
: param list partners_to_notify : optional list of partner ids restricting
the notifications to process
2012-09-12 13:38:43 +00:00
"""
notify_pids = [ ]
2013-08-27 15:38:40 +00:00
for notification in self . browse ( cr , uid , ids , context = context ) :
2014-07-06 14:44:26 +00:00
if notification . is_read :
2012-10-01 21:06:53 +00:00
continue
partner = notification . partner_id
2012-09-12 13:38:43 +00:00
# Do not send to partners without email address defined
if not partner . email :
continue
2013-03-12 14:22:57 +00:00
# Do not send to partners having same email address than the author (can cause loops or bounce effect due to messy database)
if message . author_id and message . author_id . email == partner . email :
continue
2013-03-05 15:13:12 +00:00
# Partner does not want to receive any emails or is opt-out
2014-04-09 10:16:04 +00:00
if partner . notify_email == ' none ' :
2012-09-12 13:38:43 +00:00
continue
notify_pids . append ( partner . id )
return notify_pids
2014-08-07 12:43:21 +00:00
def get_signature_footer ( self , cr , uid , user_id , res_model = None , res_id = None , context = None , user_signature = True ) :
2013-03-15 11:04:55 +00:00
""" Format a standard footer for notification emails (such as pushed messages
notification or invite emails ) .
Format :
< p > - - < br / >
Administrator
< / p >
< div >
2014-03-05 17:36:10 +00:00
< small > Sent from < a . . . > Your Company < / a > using < a . . . > OpenERP < / a > . < / small >
2013-03-15 11:04:55 +00:00
< / div >
2013-03-13 16:03:10 +00:00
"""
2013-01-24 09:42:41 +00:00
footer = " "
2013-03-15 11:04:55 +00:00
if not user_id :
return footer
# add user signature
user = self . pool . get ( " res.users " ) . browse ( cr , SUPERUSER_ID , [ user_id ] , context = context ) [ 0 ]
2014-08-07 12:43:21 +00:00
if user_signature :
if user . signature :
signature = user . signature
else :
signature = " --<br /> %s " % user . name
footer = tools . append_content_to_html ( footer , signature , plaintext = False )
2013-03-15 11:04:55 +00:00
# add company signature
2013-05-07 17:54:32 +00:00
if user . company_id . website :
2013-05-22 08:30:12 +00:00
website_url = ( ' http:// %s ' % user . company_id . website ) if not user . company_id . website . lower ( ) . startswith ( ( ' http: ' , ' https: ' ) ) \
2013-05-07 17:54:32 +00:00
else user . company_id . website
company = " <a style= ' color:inherit ' href= ' %s ' > %s </a> " % ( website_url , user . company_id . name )
2013-03-15 11:04:55 +00:00
else :
2013-05-07 17:54:32 +00:00
company = user . company_id . name
2014-08-29 11:08:06 +00:00
sent_by = _ ( ' Sent by %(company)s using %(odoo)s ' )
2014-07-23 11:16:59 +00:00
2014-08-29 11:08:06 +00:00
signature_company = ' <br /><small> %s </small> ' % ( sent_by % {
2013-08-27 15:38:40 +00:00
' company ' : company ,
2014-07-16 20:51:43 +00:00
' odoo ' : " <a style= ' color:inherit ' href= ' https://www.odoo.com/ ' >Odoo</a> "
2013-08-27 15:38:40 +00:00
} )
2013-03-15 11:04:55 +00:00
footer = tools . append_content_to_html ( footer , signature_company , plaintext = False , container_tag = ' div ' )
2013-01-24 09:42:41 +00:00
return footer
2013-08-27 15:38:40 +00:00
def update_message_notification ( self , cr , uid , ids , message_id , partner_ids , context = None ) :
existing_pids = set ( )
new_pids = set ( )
new_notif_ids = [ ]
2013-02-26 10:57:55 +00:00
2013-08-27 15:38:40 +00:00
for notification in self . browse ( cr , uid , ids , context = context ) :
existing_pids . add ( notification . partner_id . id )
2013-02-25 16:48:57 +00:00
2013-08-27 15:38:40 +00:00
# update existing notifications
2014-07-06 14:44:26 +00:00
self . write ( cr , uid , ids , { ' is_read ' : False } , context = context )
2012-09-12 13:38:43 +00:00
2013-08-27 15:38:40 +00:00
# create new notifications
new_pids = set ( partner_ids ) - existing_pids
for new_pid in new_pids :
2014-07-06 14:44:26 +00:00
new_notif_ids . append ( self . create ( cr , uid , { ' message_id ' : message_id , ' partner_id ' : new_pid , ' is_read ' : False } , context = context ) )
2013-08-27 15:38:40 +00:00
return new_notif_ids
2012-10-26 11:33:36 +00:00
2013-08-27 15:38:40 +00:00
def _notify_email ( self , cr , uid , ids , message_id , force_send = False , user_signature = True , context = None ) :
message = self . pool [ ' mail.message ' ] . browse ( cr , SUPERUSER_ID , message_id , context = context )
# compute partners
email_pids = self . get_partners_to_email ( cr , uid , ids , message , context = None )
if not email_pids :
return True
# compute email body (signature, company data)
body_html = message . body
2014-07-16 20:51:43 +00:00
# add user signature except for mail groups, where users are usually adding their own signatures already
2014-08-07 12:43:21 +00:00
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
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 ' ) )
if signature_company :
2013-06-06 12:37:24 +00:00
body_html = tools . append_content_to_html ( body_html , signature_company , plaintext = False , container_tag = ' div ' )
2012-08-30 08:51:16 +00:00
2013-08-27 15:38:40 +00:00
# compute email references
references = message . parent_id . message_id if message . parent_id else False
2013-03-20 12:16:33 +00:00
2014-06-20 14:34:27 +00:00
# custom values
custom_values = dict ( )
if message . model and message . res_id and self . pool . get ( message . model ) and hasattr ( self . pool [ message . model ] , ' message_get_email_values ' ) :
custom_values = self . pool [ message . model ] . message_get_email_values ( cr , uid , message . res_id , message , context = context )
2013-08-27 15:38:40 +00:00
# create email values
2014-06-12 11:45:21 +00:00
max_recipients = 50
2014-05-15 12:13:33 +00:00
chunks = [ email_pids [ x : x + max_recipients ] for x in xrange ( 0 , len ( email_pids ) , max_recipients ) ]
email_ids = [ ]
for chunk in chunks :
2015-11-18 17:11:52 +00:00
if message . model and message . res_id and self . pool . get ( message . model ) and hasattr ( self . pool [ message . model ] , ' message_get_recipient_values ' ) :
recipient_values = self . pool [ message . model ] . message_get_recipient_values ( cr , uid , message . res_id , notif_message = message , recipient_ids = chunk , context = context )
else :
recipient_values = self . pool [ ' mail.thread ' ] . message_get_recipient_values ( cr , uid , message . res_id , notif_message = message , recipient_ids = chunk , context = context )
2014-05-15 12:13:33 +00:00
mail_values = {
' mail_message_id ' : message . id ,
2015-03-27 13:51:00 +00:00
' auto_delete ' : ( context or { } ) . get ( ' mail_auto_delete ' , True ) ,
2015-04-17 10:52:25 +00:00
' mail_server_id ' : ( context or { } ) . get ( ' mail_server_id ' , False ) ,
2014-05-15 12:13:33 +00:00
' body_html ' : body_html ,
' references ' : references ,
}
2014-06-20 14:34:27 +00:00
mail_values . update ( custom_values )
2015-11-18 17:11:52 +00:00
mail_values . update ( recipient_values )
2014-05-15 12:13:33 +00:00
email_ids . append ( self . pool . get ( ' mail.mail ' ) . create ( cr , uid , mail_values , context = context ) )
2015-01-16 10:57:23 +00:00
# NOTE:
# 1. for more than 50 followers, use the queue system
# 2. do not send emails immediately if the registry is not loaded,
# to prevent sending email during a simple update of the database
# using the command-line.
if force_send and len ( chunks ) < 2 and \
( not self . pool . _init or
getattr ( threading . currentThread ( ) , ' testing ' , False ) ) :
2014-05-15 12:13:33 +00:00
self . pool . get ( ' mail.mail ' ) . send ( cr , uid , email_ids , context = context )
2013-06-06 10:24:37 +00:00
return True
2013-08-27 15:38:40 +00:00
def _notify ( self , cr , uid , message_id , partners_to_notify = None , context = None ,
force_send = False , user_signature = True ) :
""" Send by email the notification depending on the user preferences
: param list partners_to_notify : optional list of partner ids restricting
the notifications to process
: param bool force_send : if True , the generated mail . mail is
immediately sent after being created , as if the scheduler
was executed for this message only .
: param bool user_signature : if True , the generated mail . mail body is
the body of the related mail . message with the author ' s signature
"""
notif_ids = self . search ( cr , SUPERUSER_ID , [ ( ' message_id ' , ' = ' , message_id ) , ( ' partner_id ' , ' in ' , partners_to_notify ) ] , context = context )
# update or create notifications
new_notif_ids = self . update_message_notification ( cr , SUPERUSER_ID , notif_ids , message_id , partners_to_notify , context = context )
# mail_notify_noemail (do not send email) or no partner_ids: do not send, return
if context and context . get ( ' mail_notify_noemail ' ) :
return True
# browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
self . _notify_email ( cr , SUPERUSER_ID , new_notif_ids , message_id , force_send , user_signature , context = context )