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
2013-08-27 13:30:58 +00:00
import re
2012-12-06 14:56:32 +00:00
from openerp import tools
2012-09-05 15:53:19 +00:00
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-10-26 11:33:36 +00:00
from openerp . osv import osv , orm , fields
2012-11-07 08:59:26 +00:00
from openerp . tools import html_email_clean
2012-10-26 11:33:36 +00:00
from openerp . tools . translate import _
2013-05-23 06:02:23 +00:00
from HTMLParser import HTMLParser
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-10-26 11:33:36 +00:00
try :
from mako . template import Template as MakoTemplate
except ImportError :
_logger . warning ( " payment_acquirer: mako templates not available, payment acquirer will not work! " )
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 ] )
2013-05-23 06:02:23 +00:00
class MLStripper ( HTMLParser ) :
def __init__ ( self ) :
self . reset ( )
self . fed = [ ]
def handle_data ( self , d ) :
self . fed . append ( d )
def get_data ( self ) :
return ' ' . join ( self . fed )
def strip_tags ( html ) :
s = MLStripper ( )
s . feed ( html )
return s . get_data ( )
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 '
2012-11-02 10:46:04 +00:00
_rec_name = ' record_name '
2011-07-22 16:34:57 +00:00
2012-11-07 08:24:42 +00:00
_message_read_limit = 30
2012-10-25 13:50:20 +00:00
_message_read_fields = [ ' id ' , ' parent_id ' , ' model ' , ' res_id ' , ' body ' , ' subject ' , ' date ' , ' to_read ' , ' email_from ' ,
2012-11-29 11:30:25 +00:00
' type ' , ' vote_user_ids ' , ' attachment_ids ' , ' author_id ' , ' partner_ids ' , ' record_name ' ]
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
2012-11-21 10:19:17 +00:00
def default_get ( self , cr , uid , fields , context = None ) :
2014-02-24 13:20:38 +00:00
# print '\tmail_message: default_get on', fields
2012-11-21 10:19:17 +00:00
# protection for `default_type` values leaking from menu action context (e.g. for invoices)
if context and context . get ( ' default_type ' ) and context . get ( ' default_type ' ) not in self . _columns [ ' type ' ] . selection :
context = dict ( context , default_type = None )
2012-11-27 18:11:38 +00:00
return super ( mail_message , self ) . default_get ( cr , uid , fields , context = context )
2012-11-21 10:19:17 +00:00
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-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 )
2013-09-13 12:29:56 +00:00
partner_id = self . pool [ ' res.users ' ] . browse ( cr , SUPERUSER_ID , uid , context = context ) . partner_id . id
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-29 11:21:17 +00:00
( ' message_id ' , ' in ' , ids ) ,
( ' 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-29 11:21:17 +00:00
res [ notif . message_id . id ] = True
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-11-29 14:42:53 +00:00
return [ ' & ' , ( ' notification_ids.partner_id.user_ids ' , ' in ' , [ uid ] ) , ( ' notification_ids.read ' , ' = ' , not domain [ 0 ] [ 2 ] ) ]
2012-11-29 11:30:25 +00:00
def _get_starred ( self , cr , uid , ids , name , arg , context = None ) :
""" Compute if the message is unread by the current user. """
res = dict ( ( id , False ) for id in ids )
2013-09-13 12:29:56 +00:00
partner_id = self . pool [ ' res.users ' ] . browse ( cr , SUPERUSER_ID , uid , context = context ) . partner_id . id
2012-11-29 11:30:25 +00:00
notif_obj = self . pool . get ( ' mail.notification ' )
notif_ids = notif_obj . search ( cr , uid , [
( ' partner_id ' , ' in ' , [ partner_id ] ) ,
( ' message_id ' , ' in ' , ids ) ,
( ' starred ' , ' = ' , True ) ,
] , context = context )
for notif in notif_obj . browse ( cr , uid , notif_ids , context = context ) :
res [ notif . message_id . id ] = True
return res
def _search_starred ( self , cr , uid , obj , name , domain , context = None ) :
""" Search for messages to read by the current user. Condition is
inversed because we search unread message on a read column . """
2012-11-29 14:42:53 +00:00
return [ ' & ' , ( ' notification_ids.partner_id.user_ids ' , ' in ' , [ uid ] ) , ( ' notification_ids.starred ' , ' = ' , domain [ 0 ] [ 2 ] ) ]
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 ) :
2013-04-19 09:49:38 +00:00
name = ' %s : %s ' % ( message . subject or ' ' , strip_tags ( message . body or ' ' ) or ' ' )
2012-08-27 09:42:28 +00:00
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-10-25 13:50:20 +00:00
' email_from ' : fields . char ( ' From ' ,
2012-10-29 11:21:17 +00:00
help = " Email address of the sender. This field is set when no matching partner is found for incoming emails. " ) ,
2013-02-25 16:48:57 +00:00
' reply_to ' : fields . char ( ' Reply-To ' ,
help = ' Reply email address. Setting the reply_to bypasses the automatic thread creation. ' ) ,
2012-11-12 14:04:08 +00:00
' author_id ' : fields . many2one ( ' res.partner ' , ' Author ' , select = 1 ,
ondelete = ' set null ' ,
2012-10-29 11:21:17 +00:00
help = " Author of the message. If not set, email_from may hold an email address that did not match any partner. " ) ,
2013-05-28 14:37:06 +00:00
' author_avatar ' : fields . related ( ' author_id ' , ' image_small ' , type = " binary " , string = " Author ' s Avatar " ) ,
2012-10-25 11:30:48 +00:00
' partner_ids ' : fields . many2many ( ' res.partner ' , string = ' Recipients ' ) ,
' notified_partner_ids ' : fields . many2many ( ' res.partner ' , ' mail_notification ' ,
2012-11-02 14:23:22 +00:00
' message_id ' , ' partner_id ' , ' Notified partners ' ,
help = ' Partners that have a notification pushing this message in their mailboxes ' ) ,
2012-08-23 18:54:43 +00:00
' attachment_ids ' : fields . many2many ( ' ir.attachment ' , ' message_attachment_rel ' ,
' message_id ' , ' attachment_id ' , ' Attachments ' ) ,
2012-11-12 14:04:08 +00:00
' 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 ) ,
2014-02-24 13:20:38 +00:00
' record_name ' : fields . char ( ' Message Record Name ' , help = " Name get of the related document. " ) ,
2012-11-12 14:04:08 +00:00
' notification_ids ' : fields . one2many ( ' mail.notification ' , ' message_id ' ,
2012-12-05 15:36:09 +00:00
string = ' Notifications ' , auto_join = True ,
2012-11-12 14:04:08 +00:00
help = ' Technical field holding the message notifications. Use notified_partner_ids to access notified partners. ' ) ,
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 ' ,
2012-11-29 11:30:25 +00:00
help = ' Current user has an unread notification linked to this message ' ) ,
' starred ' : fields . function ( _get_starred , fnct_search = _search_starred ,
type = ' boolean ' , string = ' Starred ' ,
help = ' Current user has a starred notification linked to this message ' ) ,
2012-11-12 14:04:08 +00:00
' subtype_id ' : fields . many2one ( ' mail.message.subtype ' , ' Subtype ' ,
ondelete = ' set null ' , select = 1 , ) ,
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 ' ) ,
2013-07-22 13:17:25 +00:00
' mail_server_id ' : fields . many2one ( ' ir.mail_server ' , ' Outgoing mail server ' , readonly = 1 ) ,
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-11-29 14:42:53 +00:00
return [ ( ' to_read ' , ' = ' , True ) ]
2012-08-22 13:37:23 +00:00
2013-06-06 10:24:37 +00:00
def _get_default_from ( self , cr , uid , context = None ) :
this = self . pool . get ( ' res.users ' ) . browse ( cr , SUPERUSER_ID , uid , context = context )
2013-12-20 11:52:13 +00:00
if this . alias_name and this . alias_domain :
2013-06-06 10:24:37 +00:00
return ' %s < %s @ %s > ' % ( this . name , this . alias_name , this . alias_domain )
elif this . email :
return ' %s < %s > ' % ( this . name , this . email )
raise osv . except_osv ( _ ( ' Invalid Action! ' ) , _ ( " Unable to send email, please configure the sender ' s email address or alias. " ) )
2012-08-28 09:53:23 +00:00
def _get_default_author ( self , cr , uid , context = None ) :
2013-08-27 13:30:58 +00:00
return self . pool . get ( ' res.users ' ) . browse ( cr , SUPERUSER_ID , uid , context = context ) . partner_id . id
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 ' ,
2013-10-28 13:50:50 +00:00
' date ' : fields . datetime . now ,
2013-02-25 16:48:57 +00:00
' author_id ' : lambda self , cr , uid , ctx = None : self . _get_default_author ( cr , uid , ctx ) ,
2012-09-04 11:54:16 +00:00
' body ' : ' ' ,
2013-06-06 10:24:37 +00:00
' email_from ' : lambda self , cr , uid , ctx = None : self . _get_default_from ( cr , uid , ctx ) ,
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-12-10 11:28:30 +00:00
# download an attachment
2012-10-12 15:25:05 +00:00
#------------------------------------------------------
2012-12-10 11:28:30 +00:00
def download_attachment ( self , cr , uid , id_message , attachment_id , context = None ) :
""" Return the content of linked attachments. """
2012-12-10 13:35:40 +00:00
message = self . browse ( cr , uid , id_message , context = context )
if attachment_id in [ attachment . id for attachment in message . attachment_ids ] :
attachment = self . pool . get ( ' ir.attachment ' ) . browse ( cr , SUPERUSER_ID , attachment_id , context = context )
if attachment . datas and attachment . datas_fname :
2012-12-10 11:28:30 +00:00
return {
2012-12-10 13:35:40 +00:00
' base64 ' : attachment . datas ,
' filename ' : attachment . datas_fname ,
2012-12-10 11:28:30 +00:00
}
return False
2012-10-12 15:25:05 +00:00
#------------------------------------------------------
2012-11-29 11:30:25 +00:00
# Notification API
2012-10-12 15:25:05 +00:00
#------------------------------------------------------
2012-12-20 20:53:28 +00:00
def set_message_read ( self , cr , uid , msg_ids , read , create_missing = True , context = None ) :
2012-11-29 11:30:25 +00:00
""" Set messages as (un)read. Technically, the notifications related
to uid are set to ( un ) read . If for some msg_ids there are missing
notifications ( i . e . due to load more or thread parent fetching ) ,
they are created .
: param bool read : set notification as ( un ) read
2012-12-20 20:53:28 +00:00
: param bool create_missing : create notifications for missing entries
( i . e . when acting on displayed messages not notified )
2013-04-17 13:32:05 +00:00
: return number of message mark as read
2012-11-29 11:30:25 +00:00
"""
notification_obj = self . pool . get ( ' mail.notification ' )
2013-09-13 12:29:56 +00:00
user_pid = self . pool [ ' res.users ' ] . browse ( cr , SUPERUSER_ID , uid , context = context ) . partner_id . id
2012-12-20 20:53:28 +00:00
domain = [ ( ' partner_id ' , ' = ' , user_pid ) , ( ' message_id ' , ' in ' , msg_ids ) ]
if not create_missing :
domain + = [ ( ' read ' , ' = ' , not read ) ]
notif_ids = notification_obj . search ( cr , uid , domain , context = context )
2012-11-29 11:30:25 +00:00
# all message have notifications: already set them as (un)read
2012-12-20 20:53:28 +00:00
if len ( notif_ids ) == len ( msg_ids ) or not create_missing :
2013-04-17 13:32:05 +00:00
notification_obj . write ( cr , uid , notif_ids , { ' read ' : read } , context = context )
return len ( notif_ids )
2012-11-29 11:30:25 +00:00
# some messages do not have notifications: find which one, create notification, update read status
notified_msg_ids = [ notification . message_id . id for notification in notification_obj . browse ( cr , uid , notif_ids , context = context ) ]
to_create_msg_ids = list ( set ( msg_ids ) - set ( notified_msg_ids ) )
for msg_id in to_create_msg_ids :
notification_obj . create ( cr , uid , { ' partner_id ' : user_pid , ' read ' : read , ' message_id ' : msg_id } , context = context )
2013-04-17 13:32:05 +00:00
notification_obj . write ( cr , uid , notif_ids , { ' read ' : read } , context = context )
return len ( notif_ids )
2012-11-29 11:30:25 +00:00
2012-12-20 20:53:28 +00:00
def set_message_starred ( self , cr , uid , msg_ids , starred , create_missing = True , context = None ) :
2012-11-29 11:30:25 +00:00
""" Set messages as (un)starred. Technically, the notifications related
2012-12-20 20:53:28 +00:00
to uid are set to ( un ) starred .
2012-11-29 11:30:25 +00:00
: param bool starred : set notification as ( un ) starred
2012-12-20 20:53:28 +00:00
: param bool create_missing : create notifications for missing entries
( i . e . when acting on displayed messages not notified )
2012-11-29 11:30:25 +00:00
"""
notification_obj = self . pool . get ( ' mail.notification ' )
2013-09-13 12:29:56 +00:00
user_pid = self . pool [ ' res.users ' ] . browse ( cr , SUPERUSER_ID , uid , context = context ) . partner_id . id
2012-12-20 20:53:28 +00:00
domain = [ ( ' partner_id ' , ' = ' , user_pid ) , ( ' message_id ' , ' in ' , msg_ids ) ]
if not create_missing :
2012-12-20 21:12:21 +00:00
domain + = [ ( ' starred ' , ' = ' , not starred ) ]
2013-03-27 11:20:49 +00:00
values = {
' starred ' : starred
}
if starred :
values [ ' read ' ] = False
2013-05-28 14:37:06 +00:00
2012-12-20 20:53:28 +00:00
notif_ids = notification_obj . search ( cr , uid , domain , context = context )
2012-11-29 11:30:25 +00:00
# all message have notifications: already set them as (un)starred
2012-12-20 20:53:28 +00:00
if len ( notif_ids ) == len ( msg_ids ) or not create_missing :
2013-03-27 11:20:49 +00:00
notification_obj . write ( cr , uid , notif_ids , values , context = context )
2012-11-29 11:30:25 +00:00
return starred
# some messages do not have notifications: find which one, create notification, update starred status
notified_msg_ids = [ notification . message_id . id for notification in notification_obj . browse ( cr , uid , notif_ids , context = context ) ]
to_create_msg_ids = list ( set ( msg_ids ) - set ( notified_msg_ids ) )
for msg_id in to_create_msg_ids :
2013-03-27 11:20:49 +00:00
notification_obj . create ( cr , uid , dict ( values , partner_id = user_pid , message_id = msg_id ) , context = context )
notification_obj . write ( cr , uid , notif_ids , values , context = context )
2012-11-29 11:30:25 +00:00
return starred
2012-10-12 15:25:05 +00:00
2012-08-20 09:06:36 +00:00
#------------------------------------------------------
# Message loading for web interface
#------------------------------------------------------
2012-10-31 15:40:26 +00:00
def _message_read_dict_postprocess ( self , cr , uid , messages , message_tree , context = None ) :
""" Post-processing on values given by message_read. This method will
handle partners in batch to avoid doing numerous queries .
2012-10-12 13:26:00 +00:00
2012-10-31 15:40:26 +00:00
: param list messages : list of message , as get_dict result
: param dict message_tree : { [ msg . id ] : msg browse record }
2012-09-19 10:13:39 +00:00
"""
2012-10-31 15:40:26 +00:00
res_partner_obj = self . pool . get ( ' res.partner ' )
ir_attachment_obj = self . pool . get ( ' ir.attachment ' )
2013-09-13 12:29:56 +00:00
pid = self . pool [ ' res.users ' ] . browse ( cr , SUPERUSER_ID , uid , context = context ) . partner_id . id
2012-10-31 15:40:26 +00:00
# 1. Aggregate partners (author_id and partner_ids) and attachments
partner_ids = set ( )
attachment_ids = set ( )
for key , message in message_tree . iteritems ( ) :
if message . author_id :
partner_ids | = set ( [ message . author_id . id ] )
2013-06-06 10:24:37 +00:00
if message . subtype_id and message . notified_partner_ids : # take notified people of message with a subtype
2013-02-14 08:55:06 +00:00
partner_ids | = set ( [ partner . id for partner in message . notified_partner_ids ] )
2013-06-06 10:24:37 +00:00
elif not message . subtype_id and message . partner_ids : # take specified people of message without a subtype (log)
partner_ids | = set ( [ partner . id for partner in message . partner_ids ] )
2012-10-31 15:40:26 +00:00
if message . attachment_ids :
attachment_ids | = set ( [ attachment . id for attachment in message . attachment_ids ] )
2012-12-14 10:48:10 +00:00
# Read partners as SUPERUSER -> display the names like classic m2o even if no access
2012-12-12 13:36:12 +00:00
partners = res_partner_obj . name_get ( cr , SUPERUSER_ID , list ( partner_ids ) , context = context )
2012-10-31 15:40:26 +00:00
partner_tree = dict ( ( partner [ 0 ] , partner ) for partner in partners )
2012-12-14 10:48:10 +00:00
# 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see
2014-02-28 14:22:33 +00:00
attachments = ir_attachment_obj . read ( cr , SUPERUSER_ID , list ( attachment_ids ) , [ ' id ' , ' datas_fname ' , ' name ' , ' file_type_icon ' ] , context = context )
2014-01-06 13:57:27 +00:00
attachments_tree = dict ( ( attachment [ ' id ' ] , {
' id ' : attachment [ ' id ' ] ,
' filename ' : attachment [ ' datas_fname ' ] ,
' name ' : attachment [ ' name ' ] ,
2014-02-28 14:22:33 +00:00
' file_type_icon ' : attachment [ ' file_type_icon ' ] ,
2014-01-06 13:57:27 +00:00
} ) for attachment in attachments )
2012-10-31 15:40:26 +00:00
# 3. Update message dictionaries
for message_dict in messages :
message_id = message_dict . get ( ' id ' )
message = message_tree [ message_id ]
if message . author_id :
author = partner_tree [ message . author_id . id ]
else :
author = ( 0 , message . email_from )
partner_ids = [ ]
2013-06-06 10:24:37 +00:00
if message . subtype_id :
partner_ids = [ partner_tree [ partner . id ] for partner in message . notified_partner_ids
if partner . id in partner_tree ]
else :
partner_ids = [ partner_tree [ partner . id ] for partner in message . partner_ids
if partner . id in partner_tree ]
2012-09-19 10:13:39 +00:00
attachment_ids = [ ]
2012-10-31 15:40:26 +00:00
for attachment in message . attachment_ids :
if attachment . id in attachments_tree :
attachment_ids . append ( attachments_tree [ attachment . id ] )
message_dict . update ( {
' is_author ' : pid == author [ 0 ] ,
' author_id ' : author ,
' partner_ids ' : partner_ids ,
' attachment_ids ' : attachment_ids ,
2013-07-25 07:19:39 +00:00
' user_pid ' : pid
2012-10-31 15:40:26 +00:00
} )
return True
2012-09-28 13:27:23 +00:00
2012-10-31 15:40:26 +00:00
def _message_read_dict ( self , cr , uid , message , parent_id = False , context = None ) :
""" Return a dict representation of the message. This representation is
used in the JS client code , to display the messages . Partners and
attachments related stuff will be done in post - processing in batch .
2012-10-12 13:26:00 +00:00
2012-10-31 15:40:26 +00:00
: param dict message : mail . message browse record
"""
# private message: no model, no res_id
is_private = False
if not message . model or not message . res_id :
is_private = True
# votes and favorites: res.users ids, no prefetching should be done
vote_nb = len ( message . vote_user_ids )
has_voted = uid in [ user . id for user in message . vote_user_ids ]
2012-12-19 14:07:48 +00:00
try :
2013-04-25 10:43:36 +00:00
if parent_id :
max_length = 300
else :
max_length = 100
body_short = html_email_clean ( message . body , remove = False , shorten = True , max_length = max_length )
2012-12-19 14:07:48 +00:00
except Exception :
2013-04-23 15:36:45 +00:00
body_short = ' <p><b>Encoding Error : </b><br/>Unable to convert this message (id: %s ).</p> ' % message . id
2012-12-19 14:07:48 +00:00
_logger . exception ( Exception )
2012-10-31 15:40:26 +00:00
return { ' id ' : message . id ,
' type ' : message . type ,
2013-04-03 14:51:51 +00:00
' subtype ' : message . subtype_id . name if message . subtype_id else False ,
2013-04-23 15:36:45 +00:00
' body ' : message . body ,
' body_short ' : body_short ,
2012-10-31 15:40:26 +00:00
' model ' : message . model ,
' res_id ' : message . res_id ,
' record_name ' : message . record_name ,
' subject ' : message . subject ,
' date ' : message . date ,
' to_read ' : message . to_read ,
' parent_id ' : parent_id ,
' is_private ' : is_private ,
' author_id ' : False ,
2013-05-28 14:37:06 +00:00
' author_avatar ' : message . author_avatar ,
2012-10-31 15:40:26 +00:00
' is_author ' : False ,
' partner_ids ' : [ ] ,
' vote_nb ' : vote_nb ,
' has_voted ' : has_voted ,
2012-11-29 11:30:25 +00:00
' is_favorite ' : message . starred ,
2012-10-31 15:40:26 +00:00
' attachment_ids ' : [ ] ,
}
2012-10-12 13:26:00 +00:00
2012-11-02 10:22:48 +00:00
def _message_read_add_expandables ( self , cr , uid , messages , message_tree , parent_tree ,
message_unload_ids = [ ] , thread_level = 0 , domain = [ ] , parent_id = False , context = None ) :
2012-10-23 11:41:24 +00:00
""" 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
2012-10-31 15:40:26 +00:00
: param list messages : list of message structure for the Chatter
2012-10-23 11:41:24 +00:00
widget to which expandables are added
2012-10-31 15:40:26 +00:00
: param dict message_tree : dict [ id ] : browse record of this message
2012-11-02 10:22:48 +00:00
: param dict parent_tree : dict [ parent_id ] : [ child_ids ]
2012-10-31 15:40:26 +00:00
: param list message_unload_ids : list of message_ids we do not want
to load
2012-10-23 11:41:24 +00:00
: return bool : True
2012-07-20 09:25:45 +00:00
"""
2012-11-05 11:58:52 +00:00
def _get_expandable ( domain , message_nb , parent_id , max_limit ) :
2012-10-22 16:46:38 +00:00
return {
' domain ' : domain ,
' nb_messages ' : message_nb ,
' type ' : ' expandable ' ,
2012-10-26 08:00:05 +00:00
' parent_id ' : parent_id ,
2012-11-05 11:58:52 +00:00
' max_limit ' : max_limit ,
2012-10-22 16:46:38 +00:00
}
2012-10-23 11:41:24 +00:00
2012-10-31 15:40:26 +00:00
if not messages :
return True
message_ids = sorted ( message_tree . keys ( ) )
2012-10-23 11:41:24 +00:00
# 1. get the expandable for new threads
if thread_level == 0 :
2012-10-31 15:40:26 +00:00
exp_domain = domain + [ ( ' id ' , ' < ' , min ( message_unload_ids + message_ids ) ) ]
2012-10-23 11:41:24 +00:00
else :
2012-11-02 10:22:48 +00:00
exp_domain = domain + [ ' ! ' , ( ' id ' , ' child_of ' , message_unload_ids + parent_tree . keys ( ) ) ]
2012-10-23 11:41:24 +00:00
ids = self . search ( cr , uid , exp_domain , context = context , limit = 1 )
if ids :
2012-11-02 10:22:48 +00:00
# inside a thread: prepend
if parent_id :
2012-11-05 11:58:52 +00:00
messages . insert ( 0 , _get_expandable ( exp_domain , - 1 , parent_id , True ) )
2012-11-02 10:22:48 +00:00
# new threads: append
else :
2012-11-05 11:58:52 +00:00
messages . append ( _get_expandable ( exp_domain , - 1 , parent_id , True ) )
2012-10-23 11:41:24 +00:00
# 2. get the expandables for new messages inside threads if display is not flat
if thread_level == 0 :
return True
2012-10-31 15:40:26 +00:00
for message_id in message_ids :
message = message_tree [ message_id ]
2012-10-19 09:13:29 +00:00
2012-10-31 15:40:26 +00:00
# generate only for thread header messages (TDE note: parent_id may be False is uid cannot see parent_id, seems ok)
if message . parent_id :
2012-10-22 16:46:38 +00:00
continue
2012-10-31 15:40:26 +00:00
# check there are message for expandable
2012-11-02 10:22:48 +00:00
child_ids = set ( [ child . id for child in message . child_ids ] ) - set ( message_unload_ids )
child_ids = sorted ( list ( child_ids ) , reverse = True )
if not child_ids :
2012-10-22 16:46:38 +00:00
continue
2012-10-18 13:06:01 +00:00
2012-10-31 15:40:26 +00:00
# make groups of unread messages
2012-11-02 10:22:48 +00:00
id_min , id_max , nb = max ( child_ids ) , 0 , 0
for child_id in child_ids :
if not child_id in message_ids :
2012-10-17 13:44:49 +00:00
nb + = 1
2012-11-02 10:22:48 +00:00
if id_min > child_id :
id_min = child_id
if id_max < child_id :
id_max = child_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 ) ]
2012-11-14 15:58:10 +00:00
idx = [ msg . get ( ' id ' ) for msg in messages ] . index ( child_id ) + 1
# messages.append(_get_expandable(exp_domain, nb, message_id, False))
messages . insert ( idx , _get_expandable ( exp_domain , nb , message_id , False ) )
2012-11-02 10:22:48 +00:00
id_min , id_max , nb = max ( child_ids ) , 0 , 0
2012-10-03 07:28:45 +00:00
else :
2012-11-02 10:22:48 +00:00
id_min , id_max , nb = max ( child_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 ) ]
2012-11-02 10:22:48 +00:00
idx = [ msg . get ( ' id ' ) for msg in messages ] . index ( message_id ) + 1
# messages.append(_get_expandable(exp_domain, nb, message_id, id_min))
2012-11-05 11:58:52 +00:00
messages . insert ( idx , _get_expandable ( exp_domain , nb , message_id , False ) )
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-31 15:40:26 +00:00
def message_read ( self , cr , uid , ids = None , 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-31 15:40:26 +00:00
message_tree = { }
2012-10-18 13:06:01 +00:00
message_list = [ ]
2012-10-31 15:40:26 +00:00
parent_tree = { }
2012-10-02 10:52:35 +00:00
2012-10-25 07:51:10 +00:00
# no specific IDS given: fetch messages according to the domain, add their parents if uid has access to
if ids is None :
ids = self . search ( cr , uid , domain , context = context , limit = limit )
2012-10-19 13:13:58 +00:00
2012-10-31 15:40:26 +00:00
# fetch parent if threaded, sort messages
for message in self . browse ( cr , uid , ids , context = context ) :
message_id = message . id
if message_id in message_tree :
continue
message_tree [ message_id ] = message
2012-10-18 15:23:22 +00:00
2012-10-31 15:40:26 +00:00
# find parent_id
if thread_level == 0 :
tree_parent_id = parent_id
else :
tree_parent_id = message_id
parent = message
while parent . parent_id and parent . parent_id . id != parent_id :
parent = parent . parent_id
tree_parent_id = parent . id
if not parent . id in message_tree :
message_tree [ parent . id ] = parent
# newest messages first
parent_tree . setdefault ( tree_parent_id , [ ] )
if tree_parent_id != message_id :
parent_tree [ tree_parent_id ] . append ( self . _message_read_dict ( cr , uid , message_tree [ message_id ] , parent_id = tree_parent_id , context = context ) )
if thread_level :
for key , message_id_list in parent_tree . iteritems ( ) :
message_id_list . sort ( key = lambda item : item [ ' id ' ] )
message_id_list . insert ( 0 , self . _message_read_dict ( cr , uid , message_tree [ key ] , context = context ) )
2012-12-20 14:28:52 +00:00
# create final ordered message_list based on parent_tree
2012-10-31 15:40:26 +00:00
parent_list = parent_tree . items ( )
parent_list = sorted ( parent_list , key = lambda item : max ( [ msg . get ( ' id ' ) for msg in item [ 1 ] ] ) if item [ 1 ] else item [ 0 ] , reverse = True )
message_list = [ message for ( key , msg_list ) in parent_list for message in msg_list ]
2012-10-19 13:13:58 +00:00
2012-10-19 09:59:19 +00:00
# get the child expandable messages for the tree
2012-10-31 15:40:26 +00:00
self . _message_read_dict_postprocess ( cr , uid , message_list , message_tree , context = context )
2012-11-02 10:22:48 +00:00
self . _message_read_add_expandables ( cr , uid , message_list , message_tree , parent_tree ,
thread_level = thread_level , message_unload_ids = message_unload_ids , domain = domain , parent_id = parent_id , context = context )
2012-10-18 13:18:50 +00:00
return message_list
2012-10-09 08:44:36 +00:00
2012-02-02 09:48:45 +00:00
#------------------------------------------------------
2012-10-24 14:18:06 +00:00
# mail_message internals
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-12-12 17:43:23 +00:00
def _find_allowed_model_wise ( self , cr , uid , doc_model , doc_dict , context = None ) :
doc_ids = doc_dict . keys ( )
2013-03-29 14:37:20 +00:00
allowed_doc_ids = self . pool [ doc_model ] . search ( cr , uid , [ ( ' id ' , ' in ' , doc_ids ) ] , context = context )
2012-12-12 17:43:23 +00:00
return set ( [ message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict [ allowed_doc_id ] ] )
def _find_allowed_doc_ids ( self , cr , uid , model_ids , context = None ) :
model_access_obj = self . pool . get ( ' ir.model.access ' )
allowed_ids = set ( )
for doc_model , doc_dict in model_ids . iteritems ( ) :
if not model_access_obj . check ( cr , uid , doc_model , ' read ' , False ) :
continue
allowed_ids | = self . _find_allowed_model_wise ( cr , uid , doc_model , doc_dict , context = context )
return allowed_ids
2012-10-24 14:18:06 +00:00
def _search ( self , cr , uid , args , offset = 0 , limit = None , order = None ,
context = None , count = False , access_rights_uid = None ) :
""" Override that adds specific access rights of mail.message, to remove
ids uid could not see according to our custom rules . Please refer
to check_access_rule for more details about those rules .
After having received ids of a classic search , keep only :
- if author_id == pid , uid is the author , OR
- a notification ( id , pid ) exists , uid has been notified , OR
- uid have read access to the related document is model , res_id
- otherwise : remove the id
"""
# Rules do not apply to administrator
if uid == SUPERUSER_ID :
return super ( mail_message , self ) . _search ( cr , uid , args , offset = offset , limit = limit , order = order ,
2012-10-29 11:21:17 +00:00
context = context , count = count , access_rights_uid = access_rights_uid )
2012-10-24 14:18:06 +00:00
# Perform a super with count as False, to have the ids, not a counter
ids = super ( mail_message , self ) . _search ( cr , uid , args , offset = offset , limit = limit , order = order ,
context = context , count = False , access_rights_uid = access_rights_uid )
if not ids and count :
return 0
elif not ids :
return ids
2013-08-27 13:30:58 +00:00
pid = self . pool [ ' res.users ' ] . browse ( cr , SUPERUSER_ID , uid , context = context ) . partner_id . id
2012-10-24 15:53:29 +00:00
author_ids , partner_ids , allowed_ids = set ( [ ] ) , set ( [ ] ) , set ( [ ] )
2012-10-24 14:18:06 +00:00
model_ids = { }
2012-10-25 11:30:48 +00:00
messages = super ( mail_message , self ) . read ( cr , uid , ids , [ ' author_id ' , ' model ' , ' res_id ' , ' notified_partner_ids ' ] , context = context )
2012-10-24 14:18:06 +00:00
for message in messages :
if message . get ( ' author_id ' ) and message . get ( ' author_id ' ) [ 0 ] == pid :
author_ids . add ( message . get ( ' id ' ) )
2012-10-25 11:30:48 +00:00
elif pid in message . get ( ' notified_partner_ids ' ) :
2012-10-24 14:18:06 +00:00
partner_ids . add ( message . get ( ' id ' ) )
elif message . get ( ' model ' ) and message . get ( ' res_id ' ) :
model_ids . setdefault ( message . get ( ' model ' ) , { } ) . setdefault ( message . get ( ' res_id ' ) , set ( ) ) . add ( message . get ( ' id ' ) )
2012-12-12 17:43:23 +00:00
allowed_ids = self . _find_allowed_doc_ids ( cr , uid , model_ids , context = context )
2012-10-24 14:18:06 +00:00
final_ids = author_ids | partner_ids | allowed_ids
2012-12-20 14:28:52 +00:00
2012-10-24 14:18:06 +00:00
if count :
return len ( final_ids )
else :
2012-12-20 14:28:52 +00:00
# re-construct a list based on ids, because set did not keep the original order
id_list = [ id for id in ids if id in final_ids ]
return id_list
2012-10-24 14:18:06 +00:00
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
2012-10-24 15:53:29 +00:00
- author_id == pid , uid is the author , OR
- mail_notification ( id , pid ) exists , uid has been notified , OR
- uid have read access to the related document if model , res_id
- otherwise : raise
2012-09-14 11:51:07 +00:00
- create : if
2012-12-12 16:06:34 +00:00
- no model , no res_id , I create a private message OR
2012-10-24 15:53:29 +00:00
- pid in message_follower_ids if model , res_id OR
2012-12-12 16:06:34 +00:00
- mail_notification ( parent_id . id , pid ) exists , uid has been notified of the parent , OR
2013-08-21 14:37:14 +00:00
- uid have write or create access on the related document if model , res_id , OR
2012-10-24 15:53:29 +00:00
- otherwise : raise
2012-09-14 11:51:07 +00:00
- write : if
2012-12-11 14:33:01 +00:00
- author_id == pid , uid is the author , OR
2013-08-21 14:37:14 +00:00
- uid has write or create access on the related document if model , res_id
2012-12-12 16:06:34 +00:00
- otherwise : raise
2012-09-14 11:51:07 +00:00
- unlink : if
2013-08-21 14:37:14 +00:00
- uid has write or create access on the related document if model , res_id
2012-12-12 16:06:34 +00:00
- otherwise : raise
2012-09-14 11:51:07 +00:00
"""
2012-12-12 16:06:34 +00:00
def _generate_model_record_ids ( msg_val , msg_ids = [ ] ) :
""" :param model_record_ids: { ' model ' : { ' res_id ' : (msg_id, msg_id)}, ... }
: param message_values : { ' msg_id ' : { ' model ' : . . , ' res_id ' : . . , ' author_id ' : . . } }
"""
model_record_ids = { }
for id in msg_ids :
2013-10-28 13:50:50 +00:00
vals = msg_val . get ( id , { } )
if vals . get ( ' model ' ) and vals . get ( ' res_id ' ) :
model_record_ids . setdefault ( vals [ ' model ' ] , set ( ) ) . add ( vals [ ' res_id ' ] )
2012-12-12 16:06:34 +00:00
return model_record_ids
2012-09-14 11:51:07 +00:00
if uid == SUPERUSER_ID :
return
2012-05-08 13:56:00 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2012-12-12 16:06:34 +00:00
not_obj = self . pool . get ( ' mail.notification ' )
fol_obj = self . pool . get ( ' mail.followers ' )
2013-08-27 13:30:58 +00:00
partner_id = self . pool [ ' res.users ' ] . browse ( cr , SUPERUSER_ID , uid , context = None ) . partner_id . id
2012-08-15 20:01:26 +00:00
2012-09-14 11:51:07 +00:00
# Read mail_message.ids to have their values
2013-10-28 13:50:50 +00:00
message_values = dict . fromkeys ( ids , { } )
2012-12-12 16:06:34 +00:00
cr . execute ( ' SELECT DISTINCT id, model, res_id, author_id, parent_id FROM " %s " WHERE id = ANY ( %% s) ' % self . _table , ( ids , ) )
for id , rmod , rid , author_id , parent_id in cr . fetchall ( ) :
message_values [ id ] = { ' model ' : rmod , ' res_id ' : rid , ' author_id ' : author_id , ' parent_id ' : parent_id }
# Author condition (READ, WRITE, CREATE (private)) -> could become an ir.rule ?
author_ids = [ ]
2012-12-11 14:33:01 +00:00
if operation == ' read ' or operation == ' write ' :
2012-09-14 11:51:07 +00:00
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
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-12-12 16:06:34 +00:00
# Parent condition, for create (check for received notifications for the created message parent)
notified_ids = [ ]
if operation == ' create ' :
parent_ids = [ message . get ( ' parent_id ' ) for mid , message in message_values . iteritems ( )
if message . get ( ' parent_id ' ) ]
not_ids = not_obj . search ( cr , SUPERUSER_ID , [ ( ' message_id.id ' , ' in ' , parent_ids ) , ( ' partner_id ' , ' = ' , partner_id ) ] , context = context )
not_parent_ids = [ notif . message_id . id for notif in not_obj . browse ( cr , SUPERUSER_ID , not_ids , context = context ) ]
notified_ids + = [ mid for mid , message in message_values . iteritems ( )
if message . get ( ' parent_id ' ) in not_parent_ids ]
2012-09-13 17:05:00 +00:00
2012-10-24 15:53:29 +00:00
# Notification condition, for read (check for received notifications and create (in message_follower_ids)) -> could become an ir.rule, but not till we do not have a many2one variable field
2012-12-12 16:06:34 +00:00
other_ids = set ( ids ) . difference ( set ( author_ids ) , set ( notified_ids ) )
model_record_ids = _generate_model_record_ids ( message_values , other_ids )
2012-10-24 15:53:29 +00:00
if operation == ' read ' :
not_ids = not_obj . search ( cr , SUPERUSER_ID , [
( ' partner_id ' , ' = ' , partner_id ) ,
( ' message_id ' , ' in ' , ids ) ,
] , context = context )
notified_ids = [ notification . message_id . id for notification in not_obj . browse ( cr , SUPERUSER_ID , not_ids , context = context ) ]
elif operation == ' create ' :
2013-10-28 13:50:50 +00:00
for doc_model , doc_ids in model_record_ids . items ( ) :
2012-09-14 11:51:07 +00:00
fol_ids = fol_obj . search ( cr , SUPERUSER_ID , [
2012-10-24 15:53:29 +00:00
( ' res_model ' , ' = ' , doc_model ) ,
2013-10-28 13:50:50 +00:00
( ' res_id ' , ' in ' , list ( doc_ids ) ) ,
2012-09-14 11:51:07 +00:00
( ' partner_id ' , ' = ' , partner_id ) ,
] , context = context )
fol_mids = [ follower . res_id for follower in fol_obj . browse ( cr , SUPERUSER_ID , fol_ids , context = context ) ]
2012-10-24 15:53:29 +00:00
notified_ids + = [ mid for mid , message in message_values . iteritems ( )
2012-12-12 16:06:34 +00:00
if message . get ( ' model ' ) == doc_model and message . get ( ' res_id ' ) in fol_mids ]
2012-09-14 11:51:07 +00:00
# CRUD: Access rights related to the document
2012-12-12 16:06:34 +00:00
other_ids = other_ids . difference ( set ( notified_ids ) )
model_record_ids = _generate_model_record_ids ( message_values , other_ids )
2012-09-14 11:51:07 +00:00
document_related_ids = [ ]
2013-10-28 13:50:50 +00:00
for model , doc_ids in model_record_ids . items ( ) :
2013-03-29 14:37:20 +00:00
model_obj = self . pool [ model ]
2013-10-28 13:50:50 +00:00
mids = model_obj . exists ( cr , uid , list ( doc_ids ) )
2013-05-28 14:44:47 +00:00
if hasattr ( model_obj , ' check_mail_message_access ' ) :
model_obj . check_mail_message_access ( cr , uid , mids , operation , context = context )
2012-09-14 11:51:07 +00:00
else :
2013-05-28 14:44:47 +00:00
self . pool [ ' mail.thread ' ] . check_mail_message_access ( cr , uid , mids , operation , model_obj = model_obj , context = context )
2012-09-14 11:51:07 +00:00
document_related_ids + = [ mid for mid , message in message_values . iteritems ( )
2012-12-12 16:06:34 +00:00
if message . get ( ' 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-12-12 16:06:34 +00:00
other_ids = other_ids . difference ( 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
2014-02-24 13:20:38 +00:00
def _get_record_name ( self , cr , uid , values , context = None ) :
""" Return the related document name, using name_get. It is done using
SUPERUSER_ID , to be sure to have the record name correctly stored . """
if not values . get ( ' model ' ) or not values . get ( ' res_id ' ) or values [ ' model ' ] not in self . pool :
return False
return self . pool [ values [ ' model ' ] ] . name_get ( cr , SUPERUSER_ID , [ values [ ' res_id ' ] ] , context = context ) [ 0 ] [ 1 ]
2013-08-27 13:30:58 +00:00
def _get_reply_to ( self , cr , uid , values , context = None ) :
""" Return a specific reply_to: alias of the document through message_get_reply_to
or take the email_from
"""
email_reply_to = None
ir_config_parameter = self . pool . get ( " ir.config_parameter " )
catchall_domain = ir_config_parameter . get_param ( cr , uid , " mail.catchall.domain " , context = context )
# model, res_id, email_from: comes from values OR related message
model , res_id , email_from = values . get ( ' model ' ) , values . get ( ' res_id ' ) , values . get ( ' email_from ' )
# if model and res_id: try to use ``message_get_reply_to`` that returns the document alias
if not email_reply_to and model and res_id and catchall_domain and hasattr ( self . pool [ model ] , ' message_get_reply_to ' ) :
email_reply_to = self . pool [ model ] . message_get_reply_to ( cr , uid , [ res_id ] , context = context ) [ 0 ]
# no alias reply_to -> catchall alias
if not email_reply_to and catchall_domain :
catchall_alias = ir_config_parameter . get_param ( cr , uid , " mail.catchall.alias " , context = context )
if catchall_alias :
email_reply_to = ' %s @ %s ' % ( catchall_alias , catchall_domain )
# still no reply_to -> reply_to will be the email_from
if not email_reply_to and email_from :
email_reply_to = email_from
# format 'Document name <email_address>'
if email_reply_to and model and res_id :
emails = tools . email_split ( email_reply_to )
if emails :
email_reply_to = emails [ 0 ]
document_name = self . pool [ model ] . name_get ( cr , SUPERUSER_ID , [ res_id ] , context = context ) [ 0 ]
if document_name :
# sanitize document name
sanitized_doc_name = re . sub ( r ' [^ \ w+.]+ ' , ' - ' , document_name [ 1 ] )
# generate reply to
email_reply_to = _ ( ' " Followers of %s " < %s > ' ) % ( sanitized_doc_name , email_reply_to )
return email_reply_to
def _get_message_id ( self , cr , uid , values , context = None ) :
2013-11-28 13:31:32 +00:00
if values . get ( ' reply_to ' ) :
2013-08-27 13:30:58 +00:00
message_id = tools . generate_tracking_message_id ( ' reply_to ' )
2013-11-28 13:31:32 +00:00
elif values . get ( ' res_id ' ) and values . get ( ' model ' ) :
2013-08-27 13:30:58 +00:00
message_id = tools . generate_tracking_message_id ( ' %(res_id)s - %(model)s ' % values )
2013-11-28 13:31:32 +00:00
else :
2013-08-27 13:30:58 +00:00
message_id = tools . generate_tracking_message_id ( ' private ' )
return message_id
2012-05-08 13:56:00 +00:00
def create ( self , cr , uid , values , context = None ) :
2012-11-30 10:40:38 +00:00
if context is None :
context = { }
default_starred = context . pop ( ' default_starred ' , False )
2013-08-27 13:30:58 +00:00
2013-08-28 14:08:45 +00:00
if ' email_from ' not in values : # needed to compute reply_to
2013-08-27 13:30:58 +00:00
values [ ' email_from ' ] = self . _get_default_from ( cr , uid , context = context )
2013-11-28 13:31:32 +00:00
if ' message_id ' not in values :
2013-08-27 13:30:58 +00:00
values [ ' message_id ' ] = self . _get_message_id ( cr , uid , values , context = context )
2013-08-28 14:08:45 +00:00
if ' reply_to ' not in values :
2013-08-27 13:30:58 +00:00
values [ ' reply_to ' ] = self . _get_reply_to ( cr , uid , values , context = context )
2014-02-24 13:20:38 +00:00
if ' record_name ' not in values and ' default_record_name ' not in context :
values [ ' record_name ' ] = self . _get_record_name ( cr , uid , values , context = context )
2013-08-27 13:30:58 +00:00
2012-08-15 13:36:43 +00:00
newid = super ( mail_message , self ) . create ( cr , uid , values , context )
2014-02-24 13:20:38 +00:00
if not values . get ( ' subtype_id ' ) :
return newid
2013-06-06 12:37:24 +00:00
self . _notify ( cr , uid , newid , context = context ,
2013-07-22 13:17:25 +00:00
force_send = context . get ( ' mail_notify_force_send ' , True ) ,
user_signature = context . get ( ' mail_notify_user_signature ' , True ) )
2012-11-30 10:40:38 +00:00
# TDE FIXME: handle default_starred. Why not setting an inv on starred ?
# Because starred will call set_message_starred, that looks for notifications.
# When creating a new mail_message, it will create a notification to a message
# that does not exist, leading to an error (key not existing). Also this
# this means unread notifications will be created, yet we can not assure
# this is what we want.
if default_starred :
self . set_message_starred ( cr , uid , [ newid ] , True , 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 :
2013-03-06 13:46:12 +00:00
if attach . res_model == self . _name and ( attach . res_id == message . id or attach . res_id == 0 ) :
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-26 11:33:36 +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 = { }
default . update ( message_id = False , headers = False )
return super ( mail_message , self ) . copy ( cr , uid , id , default = default , context = context )
2012-10-24 14:18:06 +00:00
#------------------------------------------------------
# Messaging API
#------------------------------------------------------
2013-06-06 12:37:24 +00:00
def _notify ( self , cr , uid , newid , context = None , force_send = False , user_signature = True ) :
2012-10-03 14:27:12 +00:00
""" 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
"""
2012-12-11 14:33:01 +00:00
notification_obj = self . pool . get ( ' mail.notification ' )
2012-12-03 13:12:06 +00:00
message = self . browse ( cr , uid , newid , context = context )
2012-08-30 12:21:12 +00:00
partners_to_notify = set ( [ ] )
2013-02-25 16:48:57 +00:00
2013-06-06 10:24:37 +00:00
# all followers of the mail.message document have to be added as partners and notified if a subtype is defined (otherwise: log message)
if message . subtype_id and message . model and message . res_id :
2012-09-20 10:17:04 +00:00
fol_obj = self . pool . get ( " mail.followers " )
2012-12-14 10:48:10 +00:00
# browse as SUPERUSER because rules could restrict the search results
2013-08-27 13:30:58 +00:00
fol_ids = fol_obj . search (
cr , SUPERUSER_ID , [
( ' res_model ' , ' = ' , message . model ) ,
( ' res_id ' , ' = ' , message . res_id ) ,
( ' subtype_ids ' , ' in ' , message . subtype_id . id )
2012-10-25 11:30:48 +00:00
] , context = context )
2013-08-27 13:30:58 +00:00
partners_to_notify | = set ( fo . partner_id . id for fo in fol_obj . browse ( cr , SUPERUSER_ID , fol_ids , context = context ) )
2012-11-07 10:51:48 +00:00
# remove me from notified partners, unless the message is written on my own wall
2013-06-06 10:24:37 +00:00
if message . subtype_id and message . author_id and message . model == " res.partner " and message . res_id == message . author_id . id :
2013-08-27 13:30:58 +00:00
partners_to_notify | = set ( [ message . author_id . id ] )
2012-12-03 13:12:06 +00:00
elif message . author_id :
2013-08-27 13:30:58 +00:00
partners_to_notify - = set ( [ message . author_id . id ] )
2012-12-20 11:54:21 +00:00
# all partner_ids of the mail.message have to be notified regardless of the above (even the author if explicitly added!)
2014-02-24 14:22:36 +00:00
if message . partner_ids :
2013-08-27 13:30:58 +00:00
partners_to_notify | = set ( [ p . id for p in message . partner_ids ] )
2012-10-25 13:50:20 +00:00
2012-12-14 10:48:10 +00:00
# notify
2013-08-27 13:30:58 +00:00
notification_obj . _notify (
cr , uid , newid , partners_to_notify = list ( partners_to_notify ) , context = context ,
force_send = force_send , user_signature = user_signature
)
2012-12-14 10:48:10 +00:00
message . refresh ( )
2012-11-30 14:48:51 +00:00
2012-12-14 10:48:10 +00:00
# An error appear when a user receive a notification without notifying
# the parent message -> add a read notification for the parent
2012-12-03 13:12:06 +00:00
if message . parent_id :
2012-12-11 09:24:05 +00:00
# all notified_partner_ids of the mail.message have to be notified for the parented messages
2012-12-14 10:48:10 +00:00
partners_to_parent_notify = set ( message . notified_partner_ids ) . difference ( message . parent_id . notified_partner_ids )
2012-12-03 13:12:06 +00:00
for partner in partners_to_parent_notify :
2012-11-30 14:48:51 +00:00
notification_obj . create ( cr , uid , {
2012-12-03 13:12:06 +00:00
' message_id ' : message . parent_id . id ,
' partner_id ' : partner . id ,
2012-11-30 14:48:51 +00:00
' read ' : True ,
} , context = context )