2011-07-22 16:34:57 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
2012-03-13 13:59:49 +00:00
# Copyright (C) 2010-today OpenERP SA (<http://www.openerp.com>)
2011-07-22 16:34:57 +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/>
#
##############################################################################
import logging
2012-09-05 15:53:19 +00:00
import tools
2011-07-22 16:34:57 +00:00
from email . header import decode_header
2012-09-13 17:05:00 +00:00
from openerp import SUPERUSER_ID
2012-09-19 10:13:39 +00:00
from osv import osv , orm , fields
2012-09-12 13:35:22 +00:00
from tools . translate import _
2011-07-22 16:34:57 +00:00
2012-06-22 06:48:54 +00:00
_logger = logging . getLogger ( __name__ )
2011-07-22 16:34:57 +00:00
2012-07-06 09:41:41 +00:00
""" Some tools for parsing / creating email fields """
2011-07-22 16:34:57 +00:00
def decode ( text ) :
""" Returns unicode() string conversion of the the given encoded smtp header text """
if text :
text = decode_header ( text . replace ( ' \r ' , ' ' ) )
return ' ' . join ( [ tools . ustr ( x [ 0 ] , x [ 1 ] ) for x in text ] )
2012-09-13 15:48:44 +00:00
2012-05-08 13:56:00 +00:00
class mail_message ( osv . Model ) :
2012-08-31 09:01:20 +00:00
""" Messages model: system notification (replacing res.log notifications),
comments ( OpenChatter discussion ) and incoming emails . """
2011-07-22 16:34:57 +00:00
_name = ' mail.message '
2012-08-15 17:08:22 +00:00
_description = ' Message '
2012-08-20 19:10:58 +00:00
_inherit = [ ' ir.needaction_mixin ' ]
2012-08-15 17:08:22 +00:00
_order = ' id desc '
2011-07-22 16:34:57 +00:00
2012-08-27 09:42:28 +00:00
_message_read_limit = 10
2012-10-18 15:23:22 +00:00
_message_read_fields = [ ' id ' , ' parent_id ' , ' model ' , ' res_id ' , ' body ' , ' subject ' , ' date ' , ' to_read ' ,
' type ' , ' vote_user_ids ' , ' attachment_ids ' , ' author_id ' , ' partner_ids ' , ' record_name ' , ' favorite_user_ids ' ]
2012-08-27 09:42:28 +00:00
_message_record_name_length = 18
2012-10-18 15:23:22 +00:00
_message_read_more_limit = 1024
2012-08-27 09:42:28 +00:00
def _shorten_name ( self , name ) :
2012-09-05 15:53:19 +00:00
if len ( name ) < = ( self . _message_record_name_length + 3 ) :
2012-08-27 09:42:28 +00:00
return name
2012-08-28 17:39:01 +00:00
return name [ : self . _message_record_name_length ] + ' ... '
2012-08-27 09:42:28 +00:00
2012-08-28 09:53:23 +00:00
def _get_record_name ( self , cr , uid , ids , name , arg , context = None ) :
2012-10-18 15:23:22 +00:00
""" Return the related document name, using name_get. It is included in
a try / except statement , because if uid cannot read the related
document , he should see a void string instead of crashing . """
2012-10-17 13:44:49 +00:00
result = dict . fromkeys ( ids , False )
2012-10-17 16:12:45 +00:00
for message in self . read ( cr , uid , ids , [ ' model ' , ' res_id ' ] , context = context ) :
if not message [ ' model ' ] or not message [ ' res_id ' ] :
2012-03-27 07:30:53 +00:00
continue
2012-09-13 12:11:10 +00:00
try :
2012-10-17 16:12:45 +00:00
result [ message [ ' id ' ] ] = self . _shorten_name ( self . pool . get ( message [ ' model ' ] ) . name_get ( cr , uid , [ message [ ' res_id ' ] ] , context = context ) [ 0 ] [ 1 ] )
2012-09-19 10:13:39 +00:00
except ( orm . except_orm , osv . except_osv ) :
2012-09-13 12:11:10 +00:00
pass
2012-03-27 07:30:53 +00:00
return result
2012-06-29 15:27:58 +00:00
2012-10-17 15:57:33 +00:00
def _get_to_read ( self , cr , uid , ids , name , arg , context = None ) :
2012-08-28 09:53:23 +00:00
""" Compute if the message is unread by the current user. """
2012-10-18 13:06:01 +00:00
res = dict ( ( id , False ) for id in ids )
2012-09-14 16:16:33 +00:00
partner_id = self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = context ) [ ' partner_id ' ] [ 0 ]
2012-08-28 09:53:23 +00:00
notif_obj = self . pool . get ( ' mail.notification ' )
notif_ids = notif_obj . search ( cr , uid , [
( ' partner_id ' , ' in ' , [ partner_id ] ) ,
2012-10-17 09:35:59 +00:00
( ' message_id ' , ' in ' , ids ) ,
2012-10-19 09:59:19 +00:00
( ' read ' , ' = ' , False )
2012-08-28 09:53:23 +00:00
] , context = context )
for notif in notif_obj . browse ( cr , uid , notif_ids , context = context ) :
2012-10-17 09:35:59 +00:00
res [ notif . message_id . id ] = not notif . read
2012-08-28 09:53:23 +00:00
return res
2012-10-17 15:57:33 +00:00
def _search_to_read ( self , cr , uid , obj , name , domain , context = None ) :
""" Search for messages to read by the current user. Condition is
2012-08-31 09:01:20 +00:00
inversed because we search unread message on a read column . """
2012-08-31 11:23:53 +00:00
if domain [ 0 ] [ 2 ] :
2012-10-16 08:27:18 +00:00
read_cond = " (read = False OR read IS NULL) "
2012-08-31 11:23:53 +00:00
else :
2012-10-16 08:27:18 +00:00
read_cond = " read = True "
2012-09-17 09:51:10 +00:00
partner_id = self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = context ) [ ' partner_id ' ] [ 0 ]
2012-08-31 11:23:53 +00:00
cr . execute ( " SELECT message_id FROM mail_notification " \
" WHERE partner_id = %% s AND %s " % read_cond ,
( partner_id , ) )
2012-08-31 09:01:20 +00:00
return [ ( ' id ' , ' in ' , [ r [ 0 ] for r in cr . fetchall ( ) ] ) ]
2012-08-28 09:53:23 +00:00
2012-06-29 15:27:58 +00:00
def name_get ( self , cr , uid , ids , context = None ) :
2012-08-10 14:43:39 +00:00
# name_get may receive int id instead of an id list
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2012-06-29 15:27:58 +00:00
res = [ ]
for message in self . browse ( cr , uid , ids , context = context ) :
2012-08-27 09:42:28 +00:00
name = ' %s : %s ' % ( message . subject or ' ' , message . body or ' ' )
res . append ( ( message . id , self . _shorten_name ( name . lstrip ( ' : ' ) ) ) )
2012-06-29 15:27:58 +00:00
return res
2011-07-22 16:34:57 +00:00
_columns = {
2012-04-02 16:24:25 +00:00
' type ' : fields . selection ( [
2012-08-31 09:01:20 +00:00
( ' email ' , ' Email ' ) ,
2012-04-02 16:24:25 +00:00
( ' comment ' , ' Comment ' ) ,
( ' notification ' , ' System notification ' ) ,
2012-07-20 09:25:45 +00:00
] , ' Type ' ,
help = " Message type: email for email message, notification for system " \
2012-08-23 18:54:43 +00:00
" message, comment for other messages such as user replies " ) ,
2012-08-15 17:08:22 +00:00
' author_id ' : fields . many2one ( ' res.partner ' , ' Author ' , required = True ) ,
2012-08-23 18:54:43 +00:00
' partner_ids ' : fields . many2many ( ' res.partner ' , ' mail_notification ' , ' message_id ' , ' partner_id ' , ' Recipients ' ) ,
' attachment_ids ' : fields . many2many ( ' ir.attachment ' , ' message_attachment_rel ' ,
' message_id ' , ' attachment_id ' , ' Attachments ' ) ,
' parent_id ' : fields . many2one ( ' mail.message ' , ' Parent Message ' , select = True , ondelete = ' set null ' , help = " Initial thread message. " ) ,
2012-08-15 20:01:26 +00:00
' child_ids ' : fields . one2many ( ' mail.message ' , ' parent_id ' , ' Child Messages ' ) ,
2012-04-23 12:18:28 +00:00
' model ' : fields . char ( ' Related Document Model ' , size = 128 , select = 1 ) ,
2012-03-13 13:59:49 +00:00
' res_id ' : fields . integer ( ' Related Document ID ' , select = 1 ) ,
2012-08-28 09:53:23 +00:00
' record_name ' : fields . function ( _get_record_name , type = ' string ' ,
2012-07-06 09:41:41 +00:00
string = ' Message Record Name ' ,
help = " Name get of the related document. " ) ,
2012-08-20 11:31:50 +00:00
' notification_ids ' : fields . one2many ( ' mail.notification ' , ' message_id ' , ' Notifications ' ) ,
2012-08-23 18:54:43 +00:00
' subject ' : fields . char ( ' Subject ' ) ,
2011-07-22 16:34:57 +00:00
' date ' : fields . datetime ( ' Date ' ) ,
2012-08-23 18:54:43 +00:00
' message_id ' : fields . char ( ' Message-Id ' , help = ' Message unique identifier ' , select = 1 , readonly = 1 ) ,
2012-08-29 15:00:02 +00:00
' body ' : fields . html ( ' Contents ' , help = ' Automatically sanitized HTML contents ' ) ,
2012-10-17 15:57:33 +00:00
' to_read ' : fields . function ( _get_to_read , fnct_search = _search_to_read ,
type = ' boolean ' , string = ' To read ' ,
help = ' Functional field to search for messages the current user has to read ' ) ,
2012-08-30 11:24:26 +00:00
' subtype_id ' : fields . many2one ( ' mail.message.subtype ' , ' Subtype ' ) ,
2012-10-18 15:23:22 +00:00
' vote_user_ids ' : fields . many2many ( ' res.users ' , ' mail_vote ' ,
' message_id ' , ' user_id ' , string = ' Votes ' ,
2012-09-18 15:05:44 +00:00
help = ' Users that voted for this message ' ) ,
2012-10-18 15:23:22 +00:00
' favorite_user_ids ' : fields . many2many ( ' res.users ' , ' mail_favorite ' ,
' message_id ' , ' user_id ' , string = ' Favorite ' ,
help = ' Users that set this message in their favorites ' ) ,
2011-07-22 16:34:57 +00:00
}
2012-08-28 09:53:23 +00:00
def _needaction_domain_get ( self , cr , uid , context = None ) :
2012-08-22 13:37:23 +00:00
if self . _needaction :
2012-10-17 09:35:59 +00:00
return [ ( ' to_read ' , ' = ' , True ) ]
2012-08-22 13:37:23 +00:00
return [ ]
2012-08-28 09:53:23 +00:00
def _get_default_author ( self , cr , uid , context = None ) :
2012-10-18 15:23:22 +00:00
return self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = context ) [ ' partner_id ' ] [ 0 ]
2012-08-17 11:19:36 +00:00
2011-07-22 16:34:57 +00:00
_defaults = {
2012-04-03 17:34:49 +00:00
' type ' : ' email ' ,
2012-08-16 09:26:16 +00:00
' date ' : lambda * a : fields . datetime . now ( ) ,
2012-09-05 15:53:19 +00:00
' author_id ' : lambda self , cr , uid , ctx = { } : self . _get_default_author ( cr , uid , ctx ) ,
2012-09-04 11:54:16 +00:00
' body ' : ' ' ,
2011-07-22 16:34:57 +00:00
}
2012-09-18 12:21:39 +00:00
#------------------------------------------------------
# Vote/Like
#------------------------------------------------------
2012-10-18 15:23:22 +00:00
def vote_toggle ( self , cr , uid , ids , context = None ) :
2012-10-19 12:06:16 +00:00
''' Toggles vote. Performed using read to avoid access rights issues.
Done as SUPERUSER_ID because uid may vote for a message he cannot modify . '''
2012-10-18 15:23:22 +00:00
for message in self . read ( cr , uid , ids , [ ' vote_user_ids ' ] , context = context ) :
new_has_voted = not ( uid in message . get ( ' vote_user_ids ' ) )
if new_has_voted :
2012-10-19 12:06:16 +00:00
self . write ( cr , SUPERUSER_ID , message . get ( ' id ' ) , { ' vote_user_ids ' : [ ( 4 , uid ) ] } , context = context )
2012-10-18 15:23:22 +00:00
else :
2012-10-19 12:06:16 +00:00
self . write ( cr , SUPERUSER_ID , message . get ( ' id ' ) , { ' vote_user_ids ' : [ ( 3 , uid ) ] } , context = context )
2012-10-18 15:23:22 +00:00
return new_has_voted or False
2012-08-15 17:08:22 +00:00
2012-10-12 15:25:05 +00:00
#------------------------------------------------------
2012-10-18 15:23:22 +00:00
# Favorite
2012-10-12 15:25:05 +00:00
#------------------------------------------------------
2012-10-18 15:23:22 +00:00
def favorite_toggle ( self , cr , uid , ids , context = None ) :
2012-10-19 12:06:16 +00:00
''' Toggles favorite. Performed using read to avoid access rights issues.
Done as SUPERUSER_ID because uid may star a message he cannot modify . '''
2012-10-18 15:23:22 +00:00
for message in self . read ( cr , uid , ids , [ ' favorite_user_ids ' ] , context = context ) :
new_is_favorite = not ( uid in message . get ( ' favorite_user_ids ' ) )
if new_is_favorite :
2012-10-19 12:06:16 +00:00
self . write ( cr , SUPERUSER_ID , message . get ( ' id ' ) , { ' favorite_user_ids ' : [ ( 4 , uid ) ] } , context = context )
2012-10-18 15:23:22 +00:00
else :
2012-10-19 12:06:16 +00:00
self . write ( cr , SUPERUSER_ID , message . get ( ' id ' ) , { ' favorite_user_ids ' : [ ( 3 , uid ) ] } , context = context )
2012-10-18 15:23:22 +00:00
return new_is_favorite or False
2012-10-12 15:25:05 +00:00
2012-08-20 09:06:36 +00:00
#------------------------------------------------------
# Message loading for web interface
#------------------------------------------------------
2012-10-17 13:44:49 +00:00
def _message_get_dict ( self , cr , uid , message , context = None ) :
2012-10-18 15:23:22 +00:00
""" Return a dict representation of the message. This representation is
used in the JS client code , to display the messages .
2012-10-12 15:25:05 +00:00
2012-10-17 13:44:49 +00:00
: param dict message : read result of a mail . message
2012-09-19 10:13:39 +00:00
"""
2012-10-22 16:46:38 +00:00
is_author = False
if message [ ' author_id ' ] :
is_author = message [ ' author_id ' ] [ 0 ] == self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = None ) [ ' partner_id ' ] [ 0 ]
2012-10-18 15:23:22 +00:00
has_voted = False
2012-10-22 16:46:38 +00:00
if uid in message . get ( ' vote_user_ids ' ) :
2012-10-17 13:44:49 +00:00
has_voted = True
2012-10-18 15:23:22 +00:00
is_favorite = False
2012-10-22 16:46:38 +00:00
if uid in message . get ( ' favorite_user_ids ' ) :
2012-10-18 15:23:22 +00:00
is_favorite = True
2012-10-17 13:44:49 +00:00
2012-10-22 16:46:38 +00:00
is_private = False
if message . get ( ' model ' ) and message . get ( ' res_id ' ) :
is_private = True
2012-09-19 10:13:39 +00:00
try :
2012-10-17 13:54:51 +00:00
attachment_ids = [ { ' id ' : attach [ 0 ] , ' name ' : attach [ 1 ] } for attach in self . pool . get ( ' ir.attachment ' ) . name_get ( cr , uid , message [ ' attachment_ids ' ] , context = context ) ]
2012-09-19 10:13:39 +00:00
except ( orm . except_orm , osv . except_osv ) :
attachment_ids = [ ]
2012-10-17 13:44:49 +00:00
2012-10-22 16:46:38 +00:00
# TDE note: should we send partner_ids ?
# TDE note: shouldn't we separated followers and other partners ? costly to compute maybe ,
2012-09-19 10:13:39 +00:00
try :
2012-10-17 13:54:51 +00:00
partner_ids = self . pool . get ( ' res.partner ' ) . name_get ( cr , uid , message [ ' partner_ids ' ] , context = context )
2012-09-19 10:13:39 +00:00
except ( orm . except_orm , osv . except_osv ) :
partner_ids = [ ]
2012-09-28 13:27:23 +00:00
2012-08-20 09:39:22 +00:00
return {
2012-10-17 13:44:49 +00:00
' id ' : message [ ' id ' ] ,
' type ' : message [ ' type ' ] ,
2012-08-27 14:47:53 +00:00
' attachment_ids ' : attachment_ids ,
2012-10-17 13:44:49 +00:00
' body ' : message [ ' body ' ] ,
' model ' : message [ ' model ' ] ,
' res_id ' : message [ ' res_id ' ] ,
' record_name ' : message [ ' record_name ' ] ,
' subject ' : message [ ' subject ' ] ,
' date ' : message [ ' date ' ] ,
' author_id ' : message [ ' author_id ' ] ,
2012-10-22 16:46:38 +00:00
' is_author ' : is_author ,
2012-10-18 13:06:01 +00:00
# TDE note: is this useful ? to check
2012-08-20 10:57:49 +00:00
' partner_ids ' : partner_ids ,
2012-10-23 11:41:24 +00:00
' ancestor_id ' : False ,
2012-10-18 15:23:22 +00:00
' vote_nb ' : len ( message [ ' vote_user_ids ' ] ) ,
2012-09-27 13:48:23 +00:00
' has_voted ' : has_voted ,
2012-10-22 16:46:38 +00:00
' is_private ' : is_private ,
2012-10-18 15:23:22 +00:00
' is_favorite ' : is_favorite ,
2012-10-17 15:57:33 +00:00
' to_read ' : message [ ' to_read ' ] ,
2012-08-20 09:39:22 +00:00
}
2012-10-19 14:04:39 +00:00
def _message_read_add_expandables ( self , cr , uid , message_list , read_messages ,
2012-10-23 11:41:24 +00:00
thread_level = 0 , message_loaded_ids = [ ] , domain = [ ] , parent_id = False , context = None , limit = None ) :
""" Create expandables for message_read, to load new messages.
1. get the expandable for new threads
if display is flat ( thread_level == 0 ) :
fetch message_ids < min ( already displayed ids ) , because we
want a flat display , ordered by id
else :
fetch message_ids that are not childs of already displayed
messages
2. get the expandables for new messages inside threads if display
is not flat
for each thread header , search for its childs
for each hole in the child list based on message displayed ,
create an expandable
: param list message_list : list of message structure for the Chatter
widget to which expandables are added
2012-10-18 15:23:22 +00:00
: param dict read_messages : dict [ id ] : read result of the messages to
easily have access to their values , given their ID
2012-10-23 11:41:24 +00:00
: return bool : True
2012-10-03 07:28:45 +00:00
"""
2012-10-23 12:28:24 +00:00
def _get_expandable ( domain , message_nb , ancestor_id , id , model ) :
2012-10-22 16:46:38 +00:00
return {
' domain ' : domain ,
' nb_messages ' : message_nb ,
' type ' : ' expandable ' ,
2012-10-23 12:28:24 +00:00
' ancestor_id ' : ancestor_id ,
2012-10-22 16:46:38 +00:00
' id ' : id ,
# TDE note: why do we need model sometimes, and sometimes not ???
' model ' : model ,
}
2012-10-23 11:41:24 +00:00
# all_not_loaded_ids = []
2012-10-19 09:13:29 +00:00
id_list = sorted ( read_messages . keys ( ) )
2012-10-23 11:41:24 +00:00
# 1. get the expandable for new threads
if thread_level == 0 :
exp_domain = domain + [ ( ' id ' , ' < ' , min ( message_loaded_ids + id_list ) ) ]
else :
exp_domain = domain + [ ' ! ' , ( ' id ' , ' child_of ' , message_loaded_ids + id_list ) ]
ids = self . search ( cr , uid , exp_domain , context = context , limit = 1 )
if ids :
message_list . append ( _get_expandable ( exp_domain , - 1 , parent_id , - 1 , None ) )
# 2. get the expandables for new messages inside threads if display is not flat
if thread_level == 0 :
return True
2012-10-19 09:13:29 +00:00
for message_id in id_list :
message = read_messages [ message_id ]
2012-10-22 16:46:38 +00:00
# message is not a thread header (has a parent_id)
# TDE note: parent_id is false is there is a parent we can not see -> ok
if message . get ( ' parent_id ' ) :
continue
2012-10-19 09:59:19 +00:00
# TDE note: check search is correctly implemented in mail.message
2012-10-18 13:06:01 +00:00
not_loaded_ids = self . search ( cr , uid , [
2012-10-22 16:46:38 +00:00
( ' id ' , ' child_of ' , message [ ' id ' ] ) ,
2012-10-18 13:18:50 +00:00
( ' id ' , ' not in ' , message_loaded_ids ) ,
2012-10-18 15:23:22 +00:00
] , context = context , limit = self . _message_read_more_limit )
2012-10-22 16:46:38 +00:00
if not not_loaded_ids :
continue
# print 'not_loaded_ids', not_loaded_ids
2012-10-18 13:06:01 +00:00
2012-10-23 11:41:24 +00:00
# all_not_loaded_ids += not_loaded_ids
2012-10-22 16:46:38 +00:00
# group childs not read
id_min , id_max , nb = max ( not_loaded_ids ) , 0 , 0
2012-10-03 07:28:45 +00:00
for not_loaded_id in not_loaded_ids :
2012-10-18 15:23:22 +00:00
if not read_messages . get ( not_loaded_id ) :
2012-10-17 13:44:49 +00:00
nb + = 1
2012-10-22 16:46:38 +00:00
if id_min > not_loaded_id :
2012-10-17 13:44:49 +00:00
id_min = not_loaded_id
2012-10-22 16:46:38 +00:00
if id_max < not_loaded_id :
2012-10-17 13:44:49 +00:00
id_max = not_loaded_id
2012-10-22 16:46:38 +00:00
elif nb > 0 :
exp_domain = [ ( ' id ' , ' >= ' , id_min ) , ( ' id ' , ' <= ' , id_max ) , ( ' id ' , ' child_of ' , message_id ) ]
message_list . append ( _get_expandable ( exp_domain , nb , message_id , id_min , message . get ( ' model ' ) ) )
id_min , id_max , nb = max ( not_loaded_ids ) , 0 , 0
2012-10-03 07:28:45 +00:00
else :
2012-10-22 16:46:38 +00:00
id_min , id_max , nb = max ( not_loaded_ids ) , 0 , 0
2012-10-17 13:44:49 +00:00
if nb > 0 :
2012-10-22 16:46:38 +00:00
exp_domain = [ ( ' id ' , ' >= ' , id_min ) , ( ' id ' , ' <= ' , id_max ) , ( ' id ' , ' child_of ' , message_id ) ]
message_list . append ( _get_expandable ( exp_domain , nb , message_id , id_min , message . get ( ' model ' ) ) )
2012-10-23 11:41:24 +00:00
# message_loaded_ids = list(set(message_loaded_ids + read_messages.keys() + all_not_loaded_ids))
2012-10-03 07:28:45 +00:00
2012-10-23 11:41:24 +00:00
return True
2012-10-18 13:06:01 +00:00
2012-10-17 13:44:49 +00:00
def _get_parent ( self , cr , uid , message , context = None ) :
2012-10-18 09:34:53 +00:00
""" Tools method that tries to get the parent of a mail.message. If
2012-10-17 13:44:49 +00:00
no parent , or if uid has no access right on the parent , False
is returned .
: param dict message : read result of a mail . message
"""
if not message [ ' parent_id ' ] :
return False
parent_id = message [ ' parent_id ' ] [ 0 ]
try :
return self . read ( cr , uid , parent_id , self . _message_read_fields , context = context )
except ( orm . except_orm , osv . except_osv ) :
return False
2012-10-23 11:41:24 +00:00
def message_read ( self , cr , uid , ids = False , domain = None , message_unload_ids = None , thread_level = 0 , context = None , parent_id = False , limit = None ) :
2012-10-19 14:04:39 +00:00
""" Read messages from mail.message, and get back a list of structured
messages to be displayed as discussion threads . If IDs is set ,
2012-09-07 16:09:20 +00:00
fetch these records . Otherwise use the domain to fetch messages .
2012-10-19 14:04:39 +00:00
After having fetch messages , their ancestors will be added to obtain
well formed threads , if uid has access to them .
2012-10-23 11:41:24 +00:00
After reading the messages , expandable messages are added in the
2012-10-19 14:04:39 +00:00
message list ( see ` ` _message_read_add_expandables ` ` ) . It consists
in messages holding the ' read more ' data : number of messages to
read , domain to apply .
: param list ids : optional IDs to fetch
: param list domain : optional domain for searching ids if ids not set
: param list message_unload_ids : optional ids we do not want to fetch ,
because i . e . they are already displayed somewhere
2012-10-23 13:27:55 +00:00
: param int parent_id : context of parent_id
- if parent_id reached when adding ancestors , stop going further
in the ancestor search
- if set in flat mode , ancestor_id is set to parent_id
2012-10-19 14:04:39 +00:00
: param int limit : number of messages to fetch , before adding the
ancestors and expandables
: return list : list of message structure for the Chatter widget
2012-07-20 09:25:45 +00:00
"""
2012-10-23 11:41:24 +00:00
assert thread_level in [ 0 , 1 ] , ' message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s . ' % thread_level
2012-10-22 11:36:51 +00:00
domain = domain if domain is not None else [ ]
message_unload_ids = message_unload_ids if message_unload_ids is not None else [ ]
2012-10-19 14:04:39 +00:00
if message_unload_ids :
domain + = [ ( ' id ' , ' not in ' , message_unload_ids ) ]
2012-08-27 16:22:37 +00:00
limit = limit or self . _message_read_limit
2012-10-18 15:23:22 +00:00
read_messages = { }
2012-10-18 13:06:01 +00:00
message_list = [ ]
2012-10-02 15:12:45 +00:00
2012-10-18 15:23:22 +00:00
# specific IDs given: fetch those ids and return directly the message list
2012-10-18 13:06:01 +00:00
if ids :
2012-10-17 13:44:49 +00:00
for message in self . read ( cr , uid , ids , self . _message_read_fields , context = context ) :
2012-10-17 14:33:35 +00:00
message_list . append ( self . _message_get_dict ( cr , uid , message , context = context ) )
2012-10-18 15:23:22 +00:00
message_list = sorted ( message_list , key = lambda k : k [ ' id ' ] )
return message_list
2012-10-16 08:27:18 +00:00
2012-10-18 15:23:22 +00:00
# TDE FIXME: check access rights on search are implemented for mail.message
# fetch messages according to the domain, add their parents if uid has access to
2012-10-19 09:59:19 +00:00
ids = self . search ( cr , uid , domain , context = context , limit = limit )
for message in self . read ( cr , uid , ids , self . _message_read_fields , context = context ) :
2012-10-23 11:41:24 +00:00
# if not in tree and not in message_loaded list
2012-10-19 14:04:39 +00:00
if not read_messages . get ( message . get ( ' id ' ) ) and message . get ( ' id ' ) not in message_unload_ids :
2012-10-19 09:59:19 +00:00
read_messages [ message . get ( ' id ' ) ] = message
message_list . append ( self . _message_get_dict ( cr , uid , message , context = context ) )
2012-10-23 11:41:24 +00:00
# get the older ancestor the user can read, update its ancestor field
2012-10-23 13:27:55 +00:00
if not thread_level :
message_list [ - 1 ] [ ' ancestor_id ' ] = parent_id
continue
2012-10-19 09:59:19 +00:00
parent = self . _get_parent ( cr , uid , message , context = context )
while parent and parent . get ( ' id ' ) != parent_id :
2012-10-23 11:41:24 +00:00
message_list [ - 1 ] [ ' ancestor_id ' ] = parent . get ( ' id ' )
2012-10-22 16:46:38 +00:00
message = parent
parent = self . _get_parent ( cr , uid , message , context = context )
2012-10-23 12:28:24 +00:00
# if in thread: add its ancestor to the list of messages
2012-10-23 13:27:55 +00:00
if not read_messages . get ( message . get ( ' id ' ) ) and message . get ( ' id ' ) not in message_unload_ids :
2012-10-22 16:46:38 +00:00
read_messages [ message . get ( ' id ' ) ] = message
message_list . append ( self . _message_get_dict ( cr , uid , message , context = context ) )
2012-10-19 09:59:19 +00:00
# get the child expandable messages for the tree
message_list = sorted ( message_list , key = lambda k : k [ ' id ' ] )
2012-10-23 11:41:24 +00:00
self . _message_read_add_expandables ( cr , uid , message_list , read_messages , thread_level = thread_level ,
message_loaded_ids = message_unload_ids , domain = domain , parent_id = parent_id , context = context , limit = limit )
2012-10-18 15:23:22 +00:00
# message_list = sorted(message_list, key=lambda k: k['id'])
2012-10-18 13:18:50 +00:00
return message_list
2012-08-20 09:06:36 +00:00
2012-10-17 13:44:49 +00:00
# TDE Note: do we need this ?
2012-10-19 09:59:19 +00:00
# def user_free_attachment(self, cr, uid, context=None):
# attachment = self.pool.get('ir.attachment')
# attachment_list = []
# attachment_ids = attachment.search(cr, uid, [('res_model', '=', 'mail.message'), ('create_uid', '=', uid)])
# if len(attachment_ids):
# attachment_list = [{'id': attach.id, 'name': attach.name, 'date': attach.create_date} for attach in attachment.browse(cr, uid, attachment_ids, context=context)]
# return attachment_list
2012-10-09 13:28:24 +00:00
2012-02-02 09:48:45 +00:00
#------------------------------------------------------
2012-06-25 13:42:53 +00:00
# Email api
2012-02-02 09:48:45 +00:00
#------------------------------------------------------
2012-08-15 17:08:22 +00:00
2011-07-22 16:34:57 +00:00
def init ( self , cr ) :
cr . execute ( """ SELECT indexname FROM pg_indexes WHERE indexname = ' mail_message_model_res_id_idx ' """ )
if not cr . fetchone ( ) :
cr . execute ( """ CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id) """ )
2012-08-31 09:01:20 +00:00
def check_access_rule ( self , cr , uid , ids , operation , context = None ) :
2012-09-14 11:51:07 +00:00
""" Access rules of mail.message:
- read : if
- notification exist ( I receive pushed message ) OR
- author_id = pid ( I am the author ) OR
- I can read the related document if res_model , res_id
- Otherwise : raise
- create : if
- I am in the document message_follower_ids OR
2012-10-12 12:40:03 +00:00
- I can write on the related document if res_model , res_id OR
2012-10-19 09:59:19 +00:00
- I create a private message ( no model , no res_id )
2012-09-14 11:51:07 +00:00
- Otherwise : raise
- write : if
- I can write on the related document if res_model , res_id
- Otherwise : raise
- unlink : if
- I can write on the related document if res_model , res_id
- Otherwise : raise
"""
if uid == SUPERUSER_ID :
return
2012-05-08 13:56:00 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2012-09-14 16:16:33 +00:00
partner_id = self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = None ) [ ' partner_id ' ] [ 0 ]
2012-08-15 20:01:26 +00:00
2012-09-14 11:51:07 +00:00
# Read mail_message.ids to have their values
model_record_ids = { }
message_values = dict . fromkeys ( ids )
2012-09-17 16:04:35 +00:00
cr . execute ( ' SELECT DISTINCT id, model, res_id, author_id FROM " %s " WHERE id = ANY ( %% s) ' % self . _table , ( ids , ) )
2012-09-14 11:51:07 +00:00
for id , rmod , rid , author_id in cr . fetchall ( ) :
message_values [ id ] = { ' res_model ' : rmod , ' res_id ' : rid , ' author_id ' : author_id }
2012-09-14 16:16:33 +00:00
if rmod :
model_record_ids . setdefault ( rmod , set ( ) ) . add ( rid )
2012-09-14 11:51:07 +00:00
2012-09-14 16:16:33 +00:00
# Read: Check for received notifications -> could become an ir.rule, but not till we do not have a many2one variable field
2012-09-14 11:51:07 +00:00
if operation == ' read ' :
2012-09-13 17:05:00 +00:00
not_obj = self . pool . get ( ' mail.notification ' )
2012-09-14 11:51:07 +00:00
not_ids = not_obj . search ( cr , SUPERUSER_ID , [
2012-09-13 17:05:00 +00:00
( ' partner_id ' , ' = ' , partner_id ) ,
( ' message_id ' , ' in ' , ids ) ,
] , context = context )
2012-09-14 11:51:07 +00:00
notified_ids = [ notification . message_id . id for notification in not_obj . browse ( cr , SUPERUSER_ID , not_ids , context = context ) ]
else :
notified_ids = [ ]
2012-09-14 16:16:33 +00:00
# Read: Check messages you are author -> could become an ir.rule, but not till we do not have a many2one variable field
2012-09-14 11:51:07 +00:00
if operation == ' read ' :
author_ids = [ mid for mid , message in message_values . iteritems ( )
if message . get ( ' author_id ' ) and message . get ( ' author_id ' ) == partner_id ]
2012-10-19 09:59:19 +00:00
# Create: Check messages you create that are private messages -> ir.rule ?
elif operation == ' create ' :
author_ids = [ mid for mid , message in message_values . iteritems ( )
if not message . get ( ' model ' ) and not message . get ( ' res_id ' ) ]
2012-09-14 11:51:07 +00:00
else :
author_ids = [ ]
2012-09-13 17:05:00 +00:00
2012-10-19 09:59:19 +00:00
# Create: Check message_follower_ids
2012-09-14 11:51:07 +00:00
if operation == ' create ' :
doc_follower_ids = [ ]
for model , mids in model_record_ids . items ( ) :
fol_obj = self . pool . get ( ' mail.followers ' )
fol_ids = fol_obj . search ( cr , SUPERUSER_ID , [
( ' res_model ' , ' = ' , model ) ,
( ' res_id ' , ' in ' , list ( mids ) ) ,
( ' partner_id ' , ' = ' , partner_id ) ,
] , context = context )
fol_mids = [ follower . res_id for follower in fol_obj . browse ( cr , SUPERUSER_ID , fol_ids , context = context ) ]
doc_follower_ids + = [ mid for mid , message in message_values . iteritems ( )
2012-09-14 16:16:33 +00:00
if message . get ( ' res_model ' ) == model and message . get ( ' res_id ' ) in fol_mids ]
2012-09-14 11:51:07 +00:00
else :
doc_follower_ids = [ ]
2012-08-31 09:01:20 +00:00
2012-09-14 16:16:33 +00:00
# Calculate remaining ids, and related model/res_ids
2012-08-31 09:01:20 +00:00
model_record_ids = { }
2012-09-14 11:51:07 +00:00
other_ids = set ( ids ) . difference ( set ( notified_ids ) , set ( author_ids ) , set ( doc_follower_ids ) )
for id in other_ids :
2012-09-14 16:16:33 +00:00
if message_values [ id ] [ ' res_model ' ] :
model_record_ids . setdefault ( message_values [ id ] [ ' res_model ' ] , set ( ) ) . add ( message_values [ id ] [ ' res_id ' ] )
2012-09-14 11:51:07 +00:00
# CRUD: Access rights related to the document
document_related_ids = [ ]
2012-08-31 09:01:20 +00:00
for model , mids in model_record_ids . items ( ) :
model_obj = self . pool . get ( model )
mids = model_obj . exists ( cr , uid , mids )
2012-09-14 11:51:07 +00:00
if operation in [ ' create ' , ' write ' , ' unlink ' ] :
model_obj . check_access_rights ( cr , uid , ' write ' )
model_obj . check_access_rule ( cr , uid , mids , ' write ' , context = context )
else :
model_obj . check_access_rights ( cr , uid , operation )
model_obj . check_access_rule ( cr , uid , mids , operation , context = context )
document_related_ids + = [ mid for mid , message in message_values . iteritems ( )
if message . get ( ' res_model ' ) == model and message . get ( ' res_id ' ) in mids ]
2012-08-31 09:01:20 +00:00
2012-09-14 16:16:33 +00:00
# Calculate remaining ids: if not void, raise an error
2012-09-14 11:51:07 +00:00
other_ids = set ( ids ) . difference ( set ( notified_ids ) , set ( author_ids ) , set ( doc_follower_ids ) , set ( document_related_ids ) )
2012-09-14 16:16:33 +00:00
if not other_ids :
return
2012-09-19 10:13:39 +00:00
raise orm . except_orm ( _ ( ' Access Denied ' ) ,
2012-09-14 16:16:33 +00:00
_ ( ' The requested operation cannot be completed due to security restrictions. Please contact your system administrator. \n \n (Document type: %s , Operation: %s ) ' ) % \
( self . _description , operation ) )
2012-08-15 17:08:22 +00:00
2012-05-08 13:56:00 +00:00
def create ( self , cr , uid , values , context = None ) :
2012-08-28 17:39:01 +00:00
if not values . get ( ' message_id ' ) and values . get ( ' res_id ' ) and values . get ( ' model ' ) :
2012-10-09 13:50:40 +00:00
values [ ' message_id ' ] = tools . generate_tracking_message_id ( ' %(res_id)s - %(model)s ' % values )
2012-08-15 13:36:43 +00:00
newid = super ( mail_message , self ) . create ( cr , uid , values , context )
2012-10-04 04:46:24 +00:00
self . _notify ( cr , SUPERUSER_ID , newid , context = context )
2012-08-15 13:36:43 +00:00
return newid
2012-05-08 13:56:00 +00:00
2012-09-14 16:16:33 +00:00
def read ( self , cr , uid , ids , fields = None , context = None , load = ' _classic_read ' ) :
""" Override to explicitely call check_access_rule, that is not called
by the ORM . It instead directly fetches ir . rules and apply them . """
self . check_access_rule ( cr , uid , ids , ' read ' , context = context )
2012-10-11 16:16:14 +00:00
res = super ( mail_message , self ) . read ( cr , uid , ids , fields = fields , context = context , load = load )
2012-09-14 16:16:33 +00:00
return res
2012-09-05 15:19:50 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
# cascade-delete attachments that are directly attached to the message (should only happen
2012-09-07 11:54:22 +00:00
# for mail.messages that act as parent for a standalone mail.mail record).
2012-10-11 16:16:14 +00:00
self . check_access_rule ( cr , uid , ids , ' unlink ' , context = context )
2012-09-05 15:19:50 +00:00
attachments_to_delete = [ ]
2012-09-06 13:42:01 +00:00
for message in self . browse ( cr , uid , ids , context = context ) :
for attach in message . attachment_ids :
2012-09-07 11:54:22 +00:00
if attach . res_model == self . _name and attach . res_id == message . id :
2012-09-05 15:19:50 +00:00
attachments_to_delete . append ( attach . id )
if attachments_to_delete :
self . pool . get ( ' ir.attachment ' ) . unlink ( cr , uid , attachments_to_delete , context = context )
2012-09-06 12:52:17 +00:00
return super ( mail_message , self ) . unlink ( cr , uid , ids , context = context )
2012-09-05 15:19:50 +00:00
2012-10-03 14:27:12 +00:00
def _notify_followers ( self , cr , uid , newid , message , context = None ) :
2012-08-23 18:06:48 +00:00
""" Add the related record followers to the destination partner_ids.
"""
2012-08-30 12:21:12 +00:00
partners_to_notify = set ( [ ] )
2012-09-20 10:17:04 +00:00
# message has no subtype_id: pure log message -> no partners, no one notified
if not message . subtype_id :
message . write ( { ' partner_ids ' : [ 5 ] } )
return True
# all partner_ids of the mail.message have to be notified
2012-08-23 18:06:48 +00:00
if message . partner_ids :
2012-08-31 08:42:35 +00:00
partners_to_notify | = set ( partner . id for partner in message . partner_ids )
2012-09-20 10:17:04 +00:00
# all followers of the mail.message document have to be added as partners and notified
2012-08-23 18:06:48 +00:00
if message . model and message . res_id :
2012-09-20 10:17:04 +00:00
fol_obj = self . pool . get ( " mail.followers " )
fol_ids = fol_obj . search ( cr , uid , [ ( ' res_model ' , ' = ' , message . model ) , ( ' res_id ' , ' = ' , message . res_id ) , ( ' subtype_ids ' , ' in ' , message . subtype_id . id ) ] , context = context )
fol_objs = fol_obj . browse ( cr , uid , fol_ids , context = context )
extra_notified = set ( fol . partner_id . id for fol in fol_objs )
2012-08-30 12:21:12 +00:00
missing_notified = extra_notified - partners_to_notify
2012-09-20 10:17:04 +00:00
if missing_notified :
2012-09-14 11:51:07 +00:00
self . write ( cr , SUPERUSER_ID , [ newid ] , { ' partner_ids ' : [ ( 4 , p_id ) for p_id in missing_notified ] } , context = context )
2012-09-28 13:27:23 +00:00
2012-10-03 14:27:12 +00:00
def _notify ( self , cr , uid , newid , context = None ) :
""" Add the related record followers to the destination partner_ids if is not a private message.
Call mail_notification . notify to manage the email sending
"""
message = self . browse ( cr , uid , newid , context = context )
2012-10-18 13:06:01 +00:00
if message . model and message . res_id :
2012-10-03 14:27:12 +00:00
self . _notify_followers ( cr , uid , newid , message , context = context )
2012-10-17 14:19:33 +00:00
# add myself if I wrote on my wall, otherwise remove myself author
if ( ( message . model == " res.partner " and message . res_id == message . author_id . id ) ) :
2012-10-05 10:46:54 +00:00
self . write ( cr , SUPERUSER_ID , [ newid ] , { ' partner_ids ' : [ ( 4 , message . author_id . id ) ] } , context = context )
else :
self . write ( cr , SUPERUSER_ID , [ newid ] , { ' partner_ids ' : [ ( 3 , message . author_id . id ) ] } , context = context )
2012-10-01 21:06:53 +00:00
self . pool . get ( ' mail.notification ' ) . _notify ( cr , uid , newid , context = context )
2012-08-23 18:06:48 +00:00
2011-08-23 17:58:09 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
""" Overridden to avoid duplicating fields that are unique to each email """
if default is None :
default = { }
2012-08-15 18:44:03 +00:00
default . update ( message_id = False , headers = False )
2012-09-05 15:53:19 +00:00
return super ( mail_message , self ) . copy ( cr , uid , id , default = default , context = context )
2012-09-12 13:35:22 +00:00
#------------------------------------------------------
# Tools
#------------------------------------------------------
2012-09-13 15:48:44 +00:00
def check_partners_email ( self , cr , uid , partner_ids , context = None ) :
2012-09-12 13:35:22 +00:00
""" Verify that selected partner_ids have an email_address defined.
Otherwise throw a warning . """
partner_wo_email_lst = [ ]
for partner in self . pool . get ( ' res.partner ' ) . browse ( cr , uid , partner_ids , context = context ) :
if not partner . email :
partner_wo_email_lst . append ( partner )
if not partner_wo_email_lst :
return { }
warning_msg = _ ( ' The following partners chosen as recipients for the email have no email address linked : ' )
for partner in partner_wo_email_lst :
warning_msg + = ' \n - %s ' % ( partner . name )
return { ' warning ' : {
' title ' : _ ( ' Partners email addresses not found ' ) ,
' message ' : warning_msg ,
}
}