2011-07-22 16:34:57 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
2012-03-13 15:06:35 +00:00
# Copyright (C) 2009-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/>
#
##############################################################################
2011-08-23 17:58:09 +00:00
import base64
2012-12-18 02:11:23 +00:00
import datetime
2012-08-22 08:38:13 +00:00
import dateutil
2011-07-22 16:34:57 +00:00
import email
import logging
2012-08-23 18:54:43 +00:00
import pytz
2013-01-08 16:13:32 +00:00
import re
2012-08-23 18:54:43 +00:00
import time
2011-07-22 16:34:57 +00:00
import xmlrpclib
2012-08-09 17:16:55 +00:00
from email . message import Message
2012-11-21 09:58:31 +00:00
2012-12-20 12:17:44 +00:00
from openerp import tools
2012-09-14 11:58:53 +00:00
from openerp import SUPERUSER_ID
2012-12-19 12:13:46 +00:00
from openerp . addons . mail . mail_message import decode
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
from openerp . tools . safe_eval import safe_eval as eval
2013-02-14 14:34:17 +00:00
from openerp . tools . translate import _
2012-08-23 18:54:43 +00:00
2012-02-01 16:21:36 +00:00
_logger = logging . getLogger ( __name__ )
2011-07-22 16:34:57 +00:00
2012-10-15 13:34:38 +00:00
2012-08-10 13:19:19 +00:00
def decode_header ( message , header , separator = ' ' ) :
2013-01-07 11:13:51 +00:00
return separator . join ( map ( decode , filter ( None , message . get_all ( header , [ ] ) ) ) )
2012-08-10 13:19:19 +00:00
2012-09-20 10:17:04 +00:00
2012-09-04 12:18:38 +00:00
class mail_thread ( osv . AbstractModel ) :
2012-08-31 08:01:03 +00:00
''' mail_thread model is meant to be inherited by any model that needs to
act as a discussion topic on which messages can be attached . Public
methods are prefixed with ` ` message_ ` ` in order to avoid name
collisions with methods of the models that will inherit from this class .
` ` mail . thread ` ` defines fields used to handle and display the
communication history . ` ` mail . thread ` ` also manages followers of
inheriting classes . All features and expected behavior are managed
by mail . thread . Widgets has been designed for the 7.0 and following
versions of OpenERP .
Inheriting classes are not required to implement any method , as the
default implementation will work for any model . However it is common
to override at least the ` ` message_new ` ` and ` ` message_update ` `
methods ( calling ` ` super ` ` ) to add model - specific behavior at
creation and update of a thread when processing incoming emails .
2012-10-15 13:23:13 +00:00
Options :
- _mail_flat_thread : if set to True , all messages without parent_id
are automatically attached to the first message posted on the
ressource . If set to False , the display of Chatter is done using
threads , and no parent_id is automatically set .
2011-07-22 16:34:57 +00:00
'''
_name = ' mail.thread '
_description = ' Email Thread '
2012-10-15 13:23:13 +00:00
_mail_flat_thread = True
2011-07-22 16:34:57 +00:00
2012-12-19 09:38:44 +00:00
# Automatic logging system if mail installed
# _track = {
# 'field': {
2012-12-20 12:17:44 +00:00
# 'module.subtype_xml': lambda self, cr, uid, obj, context=None: obj[state] == done,
# 'module.subtype_xml2': lambda self, cr, uid, obj, context=None: obj[state] != done,
2012-12-19 09:38:44 +00:00
# },
# 'field2': {
# ...
# },
# }
# where
# :param string field: field name
# :param module.subtype_xml: xml_id of a mail.message.subtype (i.e. mail.mt_comment)
# :param obj: is a browse_record
# :param function lambda: returns whether the tracking should record using this subtype
2012-12-19 00:04:02 +00:00
_track = { }
2011-07-22 16:34:57 +00:00
2012-08-15 13:36:43 +00:00
def _get_message_data ( self , cr , uid , ids , name , args , context = None ) :
2012-09-20 10:17:04 +00:00
""" Computes:
- message_unread : has uid unread message for the document
- message_summary : html snippet summarizing the Chatter for kanban views """
2013-02-14 14:34:17 +00:00
res = dict ( ( id , dict ( message_unread = False , message_summary = ' ' ) ) for id in ids )
2012-11-12 13:10:51 +00:00
user_pid = self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = context ) [ ' partner_id ' ] [ 0 ]
2012-08-22 11:03:13 +00:00
2012-11-12 13:10:51 +00:00
# search for unread messages, directly in SQL to improve performances
2013-02-14 14:34:17 +00:00
cr . execute ( """ SELECT m.res_id, COUNT(n.message_id) as nb FROM mail_message m
2012-11-12 13:10:51 +00:00
RIGHT JOIN mail_notification n
2012-11-16 09:50:03 +00:00
ON ( n . message_id = m . id AND n . partner_id = % s AND ( n . read = False or n . read IS NULL ) )
2013-02-14 14:34:17 +00:00
WHERE m . model = % s AND m . res_id in % s
GROUP BY m . res_id """ ,
2012-11-12 13:10:51 +00:00
( user_pid , self . _name , tuple ( ids ) , ) )
2013-02-14 14:34:17 +00:00
for result in cr . fetchall ( ) :
res [ result [ 0 ] ] [ ' message_unread ' ] = True
if result [ 1 ] :
2013-02-14 14:39:38 +00:00
title = result [ 1 ] > 1 and _ ( " You have %d unread messages " ) % result [ 1 ] or _ ( " You have one unread message " )
2013-02-14 14:34:17 +00:00
res [ result [ 0 ] ] [ ' message_summary ' ] = " <span class= ' oe_kanban_mail_new ' title= ' %s ' ><span class= ' oe_e ' >9</span> %d %s </span> " % ( title , result [ 1 ] , _ ( " New " ) )
2012-09-21 09:50:37 +00:00
return res
2012-10-15 13:23:13 +00:00
2012-09-20 10:17:04 +00:00
def _get_subscription_data ( self , cr , uid , ids , name , args , context = None ) :
""" Computes:
- message_subtype_data : data about document subtypes : which are
available , which are followed if any """
2012-10-16 11:17:53 +00:00
res = dict ( ( id , dict ( message_subtype_data = ' ' ) ) for id in ids )
2012-09-20 10:17:04 +00:00
user_pid = self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = context ) [ ' partner_id ' ] [ 0 ]
# find current model subtypes, add them to a dictionary
subtype_obj = self . pool . get ( ' mail.message.subtype ' )
subtype_ids = subtype_obj . search ( cr , uid , [ ' | ' , ( ' res_model ' , ' = ' , self . _name ) , ( ' res_model ' , ' = ' , False ) ] , context = context )
2012-09-20 11:49:47 +00:00
subtype_dict = dict ( ( subtype . name , dict ( default = subtype . default , followed = False , id = subtype . id ) ) for subtype in subtype_obj . browse ( cr , uid , subtype_ids , context = context ) )
for id in ids :
res [ id ] [ ' message_subtype_data ' ] = subtype_dict . copy ( )
2012-09-20 10:17:04 +00:00
# find the document followers, update the data
fol_obj = self . pool . get ( ' mail.followers ' )
2012-10-19 09:59:19 +00:00
fol_ids = fol_obj . search ( cr , uid , [
2012-09-20 10:17:04 +00:00
( ' partner_id ' , ' = ' , user_pid ) ,
( ' res_id ' , ' in ' , ids ) ,
( ' res_model ' , ' = ' , self . _name ) ,
] , context = context )
2012-10-19 09:59:19 +00:00
for fol in fol_obj . browse ( cr , uid , fol_ids , context = context ) :
2012-09-20 11:49:47 +00:00
thread_subtype_dict = res [ fol . res_id ] [ ' message_subtype_data ' ]
2012-09-20 10:17:04 +00:00
for subtype in fol . subtype_ids :
thread_subtype_dict [ subtype . name ] [ ' followed ' ] = True
2012-09-20 11:49:47 +00:00
res [ fol . res_id ] [ ' message_subtype_data ' ] = thread_subtype_dict
2012-10-15 13:23:13 +00:00
2012-02-03 11:21:16 +00:00
return res
2012-04-25 05:41:43 +00:00
2012-11-27 14:32:22 +00:00
def _search_message_unread ( self , cr , uid , obj = None , name = None , domain = None , context = None ) :
2012-12-05 14:05:39 +00:00
return [ ( ' message_ids.to_read ' , ' = ' , True ) ]
2012-06-14 10:09:22 +00:00
2012-10-08 15:12:34 +00:00
def _get_followers ( self , cr , uid , ids , name , arg , context = None ) :
fol_obj = self . pool . get ( ' mail.followers ' )
fol_ids = fol_obj . search ( cr , SUPERUSER_ID , [ ( ' res_model ' , ' = ' , self . _name ) , ( ' res_id ' , ' in ' , ids ) ] )
2012-10-16 11:17:53 +00:00
res = dict ( ( id , dict ( message_follower_ids = [ ] , message_is_follower = False ) ) for id in ids )
user_pid = self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = context ) [ ' partner_id ' ] [ 0 ]
2012-10-08 15:12:34 +00:00
for fol in fol_obj . browse ( cr , SUPERUSER_ID , fol_ids ) :
2012-10-16 11:17:53 +00:00
res [ fol . res_id ] [ ' message_follower_ids ' ] . append ( fol . partner_id . id )
if fol . partner_id . id == user_pid :
res [ fol . res_id ] [ ' message_is_follower ' ] = True
2012-10-08 15:12:34 +00:00
return res
def _set_followers ( self , cr , uid , id , name , value , arg , context = None ) :
2012-10-10 07:25:10 +00:00
if not value :
return
2012-10-08 15:12:34 +00:00
partner_obj = self . pool . get ( ' res.partner ' )
fol_obj = self . pool . get ( ' mail.followers ' )
# read the old set of followers, and determine the new set of followers
fol_ids = fol_obj . search ( cr , SUPERUSER_ID , [ ( ' res_model ' , ' = ' , self . _name ) , ( ' res_id ' , ' = ' , id ) ] )
old = set ( fol . partner_id . id for fol in fol_obj . browse ( cr , SUPERUSER_ID , fol_ids ) )
new = set ( old )
2012-11-02 08:27:07 +00:00
for command in value or [ ] :
2012-10-08 15:12:34 +00:00
if isinstance ( command , ( int , long ) ) :
new . add ( command )
elif command [ 0 ] == 0 :
new . add ( partner_obj . create ( cr , uid , command [ 2 ] , context = context ) )
elif command [ 0 ] == 1 :
partner_obj . write ( cr , uid , [ command [ 1 ] ] , command [ 2 ] , context = context )
new . add ( command [ 1 ] )
elif command [ 0 ] == 2 :
partner_obj . unlink ( cr , uid , [ command [ 1 ] ] , context = context )
new . discard ( command [ 1 ] )
elif command [ 0 ] == 3 :
new . discard ( command [ 1 ] )
elif command [ 0 ] == 4 :
new . add ( command [ 1 ] )
elif command [ 0 ] == 5 :
new . clear ( )
elif command [ 0 ] == 6 :
new = set ( command [ 2 ] )
# remove partners that are no longer followers
fol_ids = fol_obj . search ( cr , SUPERUSER_ID ,
[ ( ' res_model ' , ' = ' , self . _name ) , ( ' res_id ' , ' = ' , id ) , ( ' partner_id ' , ' not in ' , list ( new ) ) ] )
fol_obj . unlink ( cr , SUPERUSER_ID , fol_ids )
# add new followers
for partner_id in new - old :
fol_obj . create ( cr , SUPERUSER_ID , { ' res_model ' : self . _name , ' res_id ' : id , ' partner_id ' : partner_id } )
def _search_followers ( self , cr , uid , obj , name , args , context ) :
fol_obj = self . pool . get ( ' mail.followers ' )
res = [ ]
for field , operator , value in args :
assert field == name
fol_ids = fol_obj . search ( cr , SUPERUSER_ID , [ ( ' res_model ' , ' = ' , self . _name ) , ( ' partner_id ' , operator , value ) ] )
res_ids = [ fol . res_id for fol in fol_obj . browse ( cr , SUPERUSER_ID , fol_ids ) ]
res . append ( ( ' id ' , ' in ' , res_ids ) )
return res
2011-07-22 16:34:57 +00:00
_columns = {
2012-10-16 11:17:53 +00:00
' message_is_follower ' : fields . function ( _get_followers ,
type = ' boolean ' , string = ' Is a Follower ' , multi = ' _get_followers, ' ) ,
2012-10-08 15:12:34 +00:00
' message_follower_ids ' : fields . function ( _get_followers , fnct_inv = _set_followers ,
2012-10-16 11:17:53 +00:00
fnct_search = _search_followers , type = ' many2many ' ,
obj = ' res.partner ' , string = ' Followers ' , multi = ' _get_followers ' ) ,
2012-08-15 13:36:43 +00:00
' message_ids ' : fields . one2many ( ' mail.message ' , ' res_id ' ,
2012-09-05 15:51:21 +00:00
domain = lambda self : [ ( ' model ' , ' = ' , self . _name ) ] ,
2012-12-05 15:36:09 +00:00
auto_join = True ,
2012-09-05 15:51:21 +00:00
string = ' Messages ' ,
2012-09-04 13:36:48 +00:00
help = " Messages and communication history " ) ,
2012-11-27 14:32:22 +00:00
' message_unread ' : fields . function ( _get_message_data ,
fnct_search = _search_message_unread , multi = " _get_message_data " ,
type = ' boolean ' , string = ' Unread Messages ' ,
2012-09-04 13:36:48 +00:00
help = " If checked new messages require your attention. " ) ,
2012-08-15 13:36:43 +00:00
' message_summary ' : fields . function ( _get_message_data , method = True ,
type = ' text ' , string = ' Summary ' , multi = " _get_message_data " ,
2012-06-21 15:23:11 +00:00
help = " Holds the Chatter summary (number of messages, ...). " \
" This summary is directly in html format in order to " \
" be inserted in kanban views. " ) ,
2011-07-22 16:34:57 +00:00
}
2012-02-28 14:06:32 +00:00
#------------------------------------------------------
2012-12-18 12:25:58 +00:00
# CRUD overrides for automatic subscription and logging
2012-02-28 14:06:32 +00:00
#------------------------------------------------------
2012-04-25 05:41:43 +00:00
2012-12-18 12:25:58 +00:00
def create ( self , cr , uid , values , context = None ) :
2012-12-19 11:05:02 +00:00
""" Chatter override :
- subscribe uid
- subscribe followers of parent
- log a creation message
"""
2012-12-12 12:55:18 +00:00
if context is None :
context = { }
2012-12-18 12:25:58 +00:00
thread_id = super ( mail_thread , self ) . create ( cr , uid , values , context = context )
# subscribe uid unless asked not to
2012-12-19 17:15:01 +00:00
if not context . get ( ' mail_create_nosubscribe ' ) :
2012-12-12 12:55:18 +00:00
self . message_subscribe_users ( cr , uid , [ thread_id ] , [ uid ] , context = context )
2013-01-31 12:17:01 +00:00
self . message_auto_subscribe ( cr , uid , [ thread_id ] , values . keys ( ) , context = context )
2012-12-18 12:25:58 +00:00
2012-12-19 11:05:02 +00:00
# automatic logging unless asked not to (mainly for various testing purpose)
2012-12-19 17:15:01 +00:00
if not context . get ( ' mail_create_nolog ' ) :
2012-12-19 21:23:30 +00:00
self . message_post ( cr , uid , thread_id , body = ' Document created ' , context = context )
2012-06-04 09:33:24 +00:00
return thread_id
2012-04-25 05:41:43 +00:00
2012-12-18 12:25:58 +00:00
def write ( self , cr , uid , ids , values , context = None ) :
2012-12-19 12:31:42 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2012-12-19 16:42:39 +00:00
# Track initial values of tracked fields
2012-12-18 12:25:58 +00:00
tracked_fields = self . _get_tracked_fields ( cr , uid , values . keys ( ) , context = context )
2012-12-18 23:49:07 +00:00
if tracked_fields :
initial = self . read ( cr , uid , ids , tracked_fields . keys ( ) , context = context )
2012-12-18 12:25:58 +00:00
initial_values = dict ( ( item [ ' id ' ] , item ) for item in initial )
2012-12-19 16:42:39 +00:00
# Perform write, update followers
2012-12-18 12:25:58 +00:00
result = super ( mail_thread , self ) . write ( cr , uid , ids , values , context = context )
2013-01-30 09:09:36 +00:00
self . message_auto_subscribe ( cr , uid , ids , values . keys ( ) , context = context )
2012-12-19 16:42:39 +00:00
# Perform the tracking
2012-12-18 23:49:07 +00:00
if tracked_fields :
2012-12-19 00:42:22 +00:00
self . message_track ( cr , uid , ids , tracked_fields , initial_values , context = context )
2012-12-18 12:25:58 +00:00
return result
2012-03-13 15:06:35 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
2012-08-22 11:03:13 +00:00
""" Override unlink to delete messages and followers. This cannot be
cascaded , because link is done through ( res_model , res_id ) . """
2012-03-13 15:06:35 +00:00
msg_obj = self . pool . get ( ' mail.message ' )
2012-08-22 11:03:13 +00:00
fol_obj = self . pool . get ( ' mail.followers ' )
2012-04-20 12:42:00 +00:00
# delete messages and notifications
2012-08-22 11:03:13 +00:00
msg_ids = msg_obj . search ( cr , uid , [ ( ' model ' , ' = ' , self . _name ) , ( ' res_id ' , ' in ' , ids ) ] , context = context )
msg_obj . unlink ( cr , uid , msg_ids , context = context )
2012-12-05 15:08:27 +00:00
# delete
res = super ( mail_thread , self ) . unlink ( cr , uid , ids , context = context )
2012-08-22 11:03:13 +00:00
# delete followers
2012-12-12 10:41:41 +00:00
fol_ids = fol_obj . search ( cr , SUPERUSER_ID , [ ( ' res_model ' , ' = ' , self . _name ) , ( ' res_id ' , ' in ' , ids ) ] , context = context )
2012-12-05 15:08:27 +00:00
fol_obj . unlink ( cr , SUPERUSER_ID , fol_ids , context = context )
return res
2012-09-14 13:52:45 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
default = default or { }
default [ ' message_ids ' ] = [ ]
default [ ' message_follower_ids ' ] = [ ]
return super ( mail_thread , self ) . copy ( cr , uid , id , default = default , context = context )
2012-04-25 05:41:43 +00:00
2012-11-21 09:58:31 +00:00
#------------------------------------------------------
# Automatically log tracked fields
#------------------------------------------------------
2012-12-18 12:25:58 +00:00
def _get_tracked_fields ( self , cr , uid , updated_fields , context = None ) :
""" Return a structure of tracked fields for the current model.
: param list updated_fields : modified field names
: return list : a list of ( field_name , column_info obj ) , containing
always tracked fields and modified on_change fields
"""
2012-12-18 23:49:07 +00:00
lst = [ ]
for name , column_info in self . _all_columns . items ( ) :
2012-12-19 11:05:02 +00:00
visibility = getattr ( column_info . column , ' track_visibility ' , False )
2012-12-20 12:17:44 +00:00
if visibility == ' always ' or ( visibility == ' onchange ' and name in updated_fields ) or name in self . _track :
2012-12-18 23:49:07 +00:00
lst . append ( name )
2012-12-19 11:05:02 +00:00
if not lst :
return lst
2012-12-18 23:49:07 +00:00
return self . fields_get ( cr , uid , lst , context = context )
2012-12-19 00:42:22 +00:00
def message_track ( self , cr , uid , ids , tracked_fields , initial_values , context = None ) :
2012-12-19 11:05:02 +00:00
2012-12-27 11:34:05 +00:00
def convert_for_display ( value , col_info ) :
if not value and col_info [ ' type ' ] == ' boolean ' :
return ' False '
2012-12-18 12:25:58 +00:00
if not value :
2012-12-18 23:49:07 +00:00
return ' '
2012-12-27 11:34:05 +00:00
if col_info [ ' type ' ] == ' many2one ' :
2012-12-18 23:49:07 +00:00
return value [ 1 ]
2012-12-27 11:34:05 +00:00
if col_info [ ' type ' ] == ' selection ' :
return dict ( col_info [ ' selection ' ] ) [ value ]
2012-12-18 12:25:58 +00:00
return value
2012-12-19 16:42:39 +00:00
def format_message ( message_description , tracked_values ) :
message = ' '
if message_description :
message = ' <span> %s </span> ' % message_description
for name , change in tracked_values . items ( ) :
message + = ' <div> • <b> %s </b>: ' % change . get ( ' col_info ' )
if change . get ( ' old_value ' ) :
message + = ' %s → ' % change . get ( ' old_value ' )
message + = ' %s </div> ' % change . get ( ' new_value ' )
return message
2012-12-19 11:05:02 +00:00
if not tracked_fields :
return True
2012-12-19 00:42:22 +00:00
for record in self . read ( cr , uid , ids , tracked_fields . keys ( ) , context = context ) :
2012-12-18 23:49:07 +00:00
initial = initial_values [ record [ ' id ' ] ]
2012-12-19 00:42:22 +00:00
changes = [ ]
2012-12-18 12:25:58 +00:00
tracked_values = { }
2012-12-19 11:05:02 +00:00
2012-12-18 12:25:58 +00:00
# generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
for col_name , col_info in tracked_fields . items ( ) :
2012-12-20 11:47:30 +00:00
if record [ col_name ] == initial [ col_name ] and getattr ( self . _all_columns [ col_name ] . column , ' track_visibility ' , None ) == ' always ' :
2012-12-18 23:49:07 +00:00
tracked_values [ col_name ] = dict ( col_info = col_info [ ' string ' ] ,
2012-12-19 21:23:30 +00:00
new_value = convert_for_display ( record [ col_name ] , col_info ) )
2012-12-19 11:32:05 +00:00
elif record [ col_name ] != initial [ col_name ] :
2012-12-20 11:47:30 +00:00
if getattr ( self . _all_columns [ col_name ] . column , ' track_visibility ' , None ) in [ ' always ' , ' onchange ' ] :
2012-12-19 11:32:05 +00:00
tracked_values [ col_name ] = dict ( col_info = col_info [ ' string ' ] ,
2012-12-19 21:23:30 +00:00
old_value = convert_for_display ( initial [ col_name ] , col_info ) ,
new_value = convert_for_display ( record [ col_name ] , col_info ) )
2012-12-19 11:32:05 +00:00
if col_name in tracked_fields :
changes . append ( col_name )
2012-12-19 00:42:22 +00:00
if not changes :
2012-12-18 12:25:58 +00:00
continue
# find subtypes and post messages or log if no subtype found
2012-12-19 00:42:22 +00:00
subtypes = [ ]
for field , track_info in self . _track . items ( ) :
2012-12-19 11:05:02 +00:00
if field not in changes :
continue
2012-12-19 00:42:22 +00:00
for subtype , method in track_info . items ( ) :
if method ( self , cr , uid , record , context ) :
subtypes . append ( subtype )
2012-12-18 23:49:07 +00:00
posted = False
2012-12-18 12:25:58 +00:00
for subtype in subtypes :
2012-12-18 23:49:07 +00:00
try :
2012-12-19 11:05:02 +00:00
subtype_rec = self . pool . get ( ' ir.model.data ' ) . get_object ( cr , uid , subtype . split ( ' . ' ) [ 0 ] , subtype . split ( ' . ' ) [ 1 ] )
2012-12-19 12:13:46 +00:00
except ValueError , e :
_logger . debug ( ' subtype %s not found, giving error " %s " ' % ( subtype , e ) )
2012-12-18 12:25:58 +00:00
continue
2012-12-19 21:23:30 +00:00
message = format_message ( subtype_rec . description if subtype_rec . description else subtype_rec . name , tracked_values )
2012-12-18 23:49:07 +00:00
self . message_post ( cr , uid , record [ ' id ' ] , body = message , subtype = subtype , context = context )
posted = True
if not posted :
2012-12-19 16:42:39 +00:00
message = format_message ( ' ' , tracked_values )
2012-12-19 00:04:02 +00:00
self . message_post ( cr , uid , record [ ' id ' ] , body = message , context = context )
2012-12-18 12:25:58 +00:00
return True
2012-11-21 09:58:31 +00:00
2012-02-01 16:21:36 +00:00
#------------------------------------------------------
2012-06-21 09:37:55 +00:00
# mail.message wrappers and tools
2012-02-01 16:21:36 +00:00
#------------------------------------------------------
2012-04-25 05:41:43 +00:00
2012-08-31 17:15:07 +00:00
def _needaction_domain_get ( self , cr , uid , context = None ) :
2012-08-15 13:36:43 +00:00
if self . _needaction :
2012-08-28 09:53:23 +00:00
return [ ( ' message_unread ' , ' = ' , True ) ]
2012-08-15 13:36:43 +00:00
return [ ]
2012-08-31 17:15:07 +00:00
2013-01-02 13:00:25 +00:00
#------------------------------------------------------
# Email specific
#------------------------------------------------------
def message_get_reply_to ( self , cr , uid , ids , context = None ) :
if not self . _inherits . get ( ' mail.alias ' ) :
2013-01-02 13:50:20 +00:00
return [ False for id in ids ]
2013-01-02 13:00:25 +00:00
return [ " %s @ %s " % ( record [ ' alias_name ' ] , record [ ' alias_domain ' ] )
if record . get ( ' alias_domain ' ) and record . get ( ' alias_name ' )
else False
for record in self . read ( cr , uid , ids , [ ' alias_name ' , ' alias_domain ' ] , context = context ) ]
2012-08-20 07:42:42 +00:00
#------------------------------------------------------
# Mail gateway
#------------------------------------------------------
2011-12-09 14:28:39 +00:00
def message_capable_models ( self , cr , uid , context = None ) :
2012-09-04 14:50:11 +00:00
""" Used by the plugin addon, based for plugin_outlook and others. """
2011-12-09 14:28:39 +00:00
ret_dict = { }
for model_name in self . pool . obj_list ( ) :
model = self . pool . get ( model_name )
if ' mail.thread ' in getattr ( model , ' _inherit ' , [ ] ) :
2012-09-04 14:50:11 +00:00
ret_dict [ model_name ] = model . _description
2011-12-09 14:28:39 +00:00
return ret_dict
2012-08-22 08:38:13 +00:00
def _message_find_partners ( self , cr , uid , message , header_fields = [ ' From ' ] , context = None ) :
""" Find partners related to some header fields of the message. """
s = ' , ' . join ( [ decode ( message . get ( h ) ) for h in header_fields if message . get ( h ) ] )
2012-09-04 13:36:48 +00:00
return [ partner_id for email in tools . email_split ( s )
2012-12-19 18:10:03 +00:00
for partner_id in self . pool . get ( ' res.partner ' ) . search ( cr , uid , [ ( ' email ' , ' ilike ' , email ) ] , limit = 1 , context = context ) ]
2012-08-16 15:48:23 +00:00
2012-08-07 18:04:12 +00:00
def _message_find_user_id ( self , cr , uid , message , context = None ) :
2012-08-16 16:43:11 +00:00
from_local_part = tools . email_split ( decode ( message . get ( ' From ' ) ) ) [ 0 ]
2012-08-20 07:42:42 +00:00
# FP Note: canonification required, the minimu: .lower()
2012-09-05 15:51:21 +00:00
user_ids = self . pool . get ( ' res.users ' ) . search ( cr , uid , [ ' | ' ,
2012-08-20 07:42:42 +00:00
( ' login ' , ' = ' , from_local_part ) ,
( ' email ' , ' = ' , from_local_part ) ] , context = context )
2012-08-07 18:04:12 +00:00
return user_ids [ 0 ] if user_ids else uid
2012-06-14 14:17:32 +00:00
2012-08-07 18:04:12 +00:00
def message_route ( self , cr , uid , message , model = None , thread_id = None ,
custom_values = None , context = None ) :
""" Attempt to figure out the correct target model, thread_id,
custom_values and user_id to use for an incoming message .
2012-08-10 13:19:19 +00:00
Multiple values may be returned , if a message had multiple
recipients matching existing mail . aliases , for example .
2012-08-07 18:04:12 +00:00
2012-08-15 13:36:43 +00:00
The following heuristics are used , in this order :
2012-08-07 18:04:12 +00:00
1. If the message replies to an existing thread_id , and
properly contains the thread model in the ' In-Reply-To '
header , use this model / thread_id pair , and ignore
2012-08-15 13:36:43 +00:00
custom_value ( not needed as no creation will take place )
2012-08-07 18:04:12 +00:00
2. Look for a mail . alias entry matching the message
recipient , and use the corresponding model , thread_id ,
custom_values and user_id .
3. Fallback to the ` ` model ` ` , ` ` thread_id ` ` and ` ` custom_values ` `
provided .
4. If all the above fails , raise an exception .
: param string message : an email . message instance
: param string model : the fallback model to use if the message
does not match any of the currently configured mail aliases
( may be None if a matching alias is supposed to be present )
: type dict custom_values : optional dictionary of default field values
to pass to ` ` message_new ` ` if a new record needs to be created .
Ignored if the thread record already exists , and also if a
matching mail . alias was found ( aliases define their own defaults )
: param int thread_id : optional ID of the record / thread from ` ` model ` `
to which this mail should be attached . Only used if the message
does not reply to an existing thread and does not match any mail alias .
2012-08-10 13:19:19 +00:00
: return : list of [ model , thread_id , custom_values , user_id ]
2011-07-22 16:34:57 +00:00
"""
2012-08-09 17:16:55 +00:00
assert isinstance ( message , Message ) , ' message must be an email.message.Message at this point '
message_id = message . get ( ' Message-Id ' )
2012-11-08 15:25:02 +00:00
references = decode_header ( message , ' References ' )
in_reply_to = decode_header ( message , ' In-Reply-To ' )
2012-08-09 17:16:55 +00:00
2012-08-07 18:04:12 +00:00
# 1. Verify if this is a reply to an existing thread
2012-11-08 15:25:02 +00:00
thread_references = references or in_reply_to
ref_match = thread_references and tools . reference_re . search ( thread_references )
2012-08-07 18:04:12 +00:00
if ref_match :
thread_id = int ( ref_match . group ( 1 ) )
model = ref_match . group ( 2 ) or model
model_pool = self . pool . get ( model )
if thread_id and model and model_pool and model_pool . exists ( cr , uid , thread_id ) \
and hasattr ( model_pool , ' message_update ' ) :
2012-08-14 08:04:21 +00:00
_logger . debug ( ' Routing mail with Message-Id %s : direct reply to model: %s , thread_id: %s , custom_values: %s , uid: %s ' ,
2012-08-09 17:16:55 +00:00
message_id , model , thread_id , custom_values , uid )
2012-08-10 13:19:19 +00:00
return [ ( model , thread_id , custom_values , uid ) ]
2012-08-15 13:36:43 +00:00
2012-12-18 16:37:15 +00:00
# Verify whether this is a reply to a private message
if in_reply_to :
message_ids = self . pool . get ( ' mail.message ' ) . search ( cr , uid , [ ( ' message_id ' , ' = ' , in_reply_to ) ] , limit = 1 , context = context )
if message_ids :
message = self . pool . get ( ' mail.message ' ) . browse ( cr , uid , message_ids [ 0 ] , context = context )
_logger . debug ( ' Routing mail with Message-Id %s : direct reply to a private message: %s , custom_values: %s , uid: %s ' ,
message_id , message . id , custom_values , uid )
return [ ( message . model , message . res_id , custom_values , uid ) ]
2012-11-08 15:25:02 +00:00
2012-08-07 18:04:12 +00:00
# 2. Look for a matching mail.alias entry
# Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
# for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
2012-12-18 12:29:35 +00:00
rcpt_tos = \
' , ' . join ( [ decode_header ( message , ' Delivered-To ' ) ,
decode_header ( message , ' To ' ) ,
2012-08-14 08:04:21 +00:00
decode_header ( message , ' Cc ' ) ,
decode_header ( message , ' Resent-To ' ) ,
decode_header ( message , ' Resent-Cc ' ) ] )
2012-08-16 16:43:11 +00:00
local_parts = [ e . split ( ' @ ' ) [ 0 ] for e in tools . email_split ( rcpt_tos ) ]
2012-08-07 18:04:12 +00:00
if local_parts :
mail_alias = self . pool . get ( ' mail.alias ' )
alias_ids = mail_alias . search ( cr , uid , [ ( ' alias_name ' , ' in ' , local_parts ) ] )
2012-08-09 17:16:55 +00:00
if alias_ids :
2012-08-10 13:19:19 +00:00
routes = [ ]
for alias in mail_alias . browse ( cr , uid , alias_ids , context = context ) :
user_id = alias . alias_user_id . id
if not user_id :
2013-01-03 17:26:21 +00:00
# TDE note: this could cause crashes, because no clue that the user
# that send the email has the right to create or modify a new document
# Fallback on user_id = uid
# Note: recognized partners will be added as followers anyway
# user_id = self._message_find_user_id(cr, uid, message, context=context)
user_id = uid
2013-01-03 17:30:19 +00:00
_logger . debug ( ' No matching user_id for the alias %s ' , alias . alias_name )
2012-08-10 13:19:19 +00:00
routes . append ( ( alias . alias_model_id . model , alias . alias_force_thread_id , \
eval ( alias . alias_defaults ) , user_id ) )
_logger . debug ( ' Routing mail with Message-Id %s : direct alias match: %r ' , message_id , routes )
return routes
2012-08-15 13:36:43 +00:00
2012-08-07 18:04:12 +00:00
# 3. Fallback to the provided parameters, if they work
model_pool = self . pool . get ( model )
2012-08-10 13:19:19 +00:00
if not thread_id :
# Legacy: fallback to matching [ID] in the Subject
match = tools . res_re . search ( decode_header ( message , ' Subject ' ) )
thread_id = match and match . group ( 1 )
2012-08-07 18:04:12 +00:00
assert thread_id and hasattr ( model_pool , ' message_update ' ) or hasattr ( model_pool , ' message_new ' ) , \
" No possible route found for incoming message with Message-Id %s . " \
2012-08-14 08:04:21 +00:00
" Create an appropriate mail.alias or force the destination model. " % message_id
2012-08-10 13:19:19 +00:00
if thread_id and not model_pool . exists ( cr , uid , thread_id ) :
2012-08-09 17:16:55 +00:00
_logger . warning ( ' Received mail reply to missing document %s ! Ignoring and creating new document instead for Message-Id %s ' ,
thread_id , message_id )
thread_id = None
_logger . debug ( ' Routing mail with Message-Id %s : fallback to model: %s , thread_id: %s , custom_values: %s , uid: %s ' ,
message_id , model , thread_id , custom_values , uid )
2012-08-10 13:19:19 +00:00
return [ ( model , thread_id , custom_values , uid ) ]
2011-07-22 16:34:57 +00:00
2011-09-07 15:13:48 +00:00
def message_process ( self , cr , uid , model , message , custom_values = None ,
2011-09-08 00:16:51 +00:00
save_original = False , strip_attachments = False ,
2012-06-14 14:17:32 +00:00
thread_id = None , context = None ) :
2012-11-08 15:25:02 +00:00
""" Process an incoming RFC2822 email message, relying on
` ` mail . message . parse ( ) ` ` for the parsing operation ,
and ` ` message_route ( ) ` ` to figure out the target model .
2012-08-15 13:36:43 +00:00
2012-11-08 15:25:02 +00:00
Once the target model is known , its ` ` message_new ` ` method
is called with the new message ( if the thread record did not exist )
2012-08-20 07:26:03 +00:00
or its ` ` message_update ` ` method ( if it did ) .
2012-08-07 18:04:12 +00:00
2012-11-08 15:25:02 +00:00
There is a special case where the target model is False : a reply
to a private message . In this case , we skip the message_new /
message_update step , to just post a new message using mail_thread
message_post .
2012-08-07 18:04:12 +00:00
: param string model : the fallback model to use if the message
does not match any of the currently configured mail aliases
( may be None if a matching alias is supposed to be present )
: param message : source of the RFC2822 message
2011-07-22 16:34:57 +00:00
: type message : string or xmlrpclib . Binary
2011-09-13 13:23:40 +00:00
: type dict custom_values : optional dictionary of field values
2012-08-07 18:04:12 +00:00
to pass to ` ` message_new ` ` if a new record needs to be created .
Ignored if the thread record already exists , and also if a
matching mail . alias was found ( aliases define their own defaults )
2011-09-07 15:13:48 +00:00
: param bool save_original : whether to keep a copy of the original
2012-07-03 12:20:20 +00:00
email source attached to the message after it is imported .
2011-09-08 00:16:51 +00:00
: param bool strip_attachments : whether to strip all attachments
2012-07-03 12:20:20 +00:00
before processing the message , in order to save some space .
2012-06-14 14:17:32 +00:00
: param int thread_id : optional ID of the record / thread from ` ` model ` `
to which this mail should be attached . When provided , this
overrides the automatic detection based on the message
headers .
2011-07-22 16:34:57 +00:00
"""
2012-10-15 13:34:38 +00:00
if context is None :
context = { }
2012-08-07 18:04:12 +00:00
2011-07-22 16:34:57 +00:00
# extract message bytes - we are forced to pass the message as binary because
# we don't know its encoding until we parse its headers and hence can't
# convert it to utf-8 for transport between the mailgate script and here.
if isinstance ( message , xmlrpclib . Binary ) :
message = str ( message . data )
# Warning: message_from_string doesn't always work correctly on unicode,
# we must use utf-8 strings here :-(
if isinstance ( message , unicode ) :
message = message . encode ( ' utf-8 ' )
msg_txt = email . message_from_string ( message )
2012-08-10 13:19:19 +00:00
routes = self . message_route ( cr , uid , msg_txt , model ,
thread_id , custom_values ,
context = context )
2012-08-23 18:54:43 +00:00
msg = self . message_parse ( cr , uid , msg_txt , save_original = save_original , context = context )
2012-10-15 13:34:38 +00:00
if strip_attachments :
msg . pop ( ' attachments ' , None )
2012-12-19 18:10:03 +00:00
# postpone setting msg.partner_ids after message_post, to avoid double notifications
partner_ids = msg . pop ( ' partner_ids ' , [ ] )
2012-09-16 15:10:38 +00:00
thread_id = False
2012-08-15 13:36:43 +00:00
for model , thread_id , custom_values , user_id in routes :
2013-02-14 12:02:57 +00:00
if self . _name == ' mail.thread ' :
2012-08-10 13:19:19 +00:00
context . update ( { ' thread_model ' : model } )
2012-11-08 15:25:02 +00:00
if model :
model_pool = self . pool . get ( model )
assert thread_id and hasattr ( model_pool , ' message_update ' ) or hasattr ( model_pool , ' message_new ' ) , \
" Undeliverable mail with Message-Id %s , model %s does not accept incoming emails " % \
( msg [ ' message_id ' ] , model )
2012-12-19 18:10:03 +00:00
# disabled subscriptions during message_new/update to avoid having the system user running the
2012-12-20 11:54:41 +00:00
# email gateway become a follower of all inbound messages
nosub_ctx = dict ( context , mail_create_nosubscribe = True )
2012-11-08 15:25:02 +00:00
if thread_id and hasattr ( model_pool , ' message_update ' ) :
2012-12-19 18:10:03 +00:00
model_pool . message_update ( cr , user_id , [ thread_id ] , msg , context = nosub_ctx )
2012-11-08 15:25:02 +00:00
else :
2013-02-14 16:20:11 +00:00
nosub_ctx = dict ( nosub_ctx , mail_create_nolog = True )
2012-12-19 18:10:03 +00:00
thread_id = model_pool . message_new ( cr , user_id , msg , custom_values , context = nosub_ctx )
2012-08-10 13:19:19 +00:00
else :
2012-11-14 10:53:23 +00:00
assert thread_id == 0 , " Posting a message without model should be with a null res_id, to create a private message. "
2012-11-08 15:25:02 +00:00
model_pool = self . pool . get ( ' mail.thread ' )
2013-02-21 13:26:47 +00:00
new_msg_id = model_pool . message_post ( cr , uid , [ thread_id ] , context = context , subtype = ' mail.mt_comment ' , * * msg )
2012-12-19 18:10:03 +00:00
if partner_ids :
# postponed after message_post, because this is an external message and we don't want to create
# duplicate emails due to notifications
self . pool . get ( ' mail.message ' ) . write ( cr , uid , [ new_msg_id ] , { ' partner_ids ' : partner_ids } , context = context )
2012-09-13 07:17:24 +00:00
return thread_id
2011-07-22 16:34:57 +00:00
def message_new ( self , cr , uid , msg_dict , custom_values = None , context = None ) :
2011-08-23 17:58:09 +00:00
""" Called by ``message_process`` when a new message is received
2012-04-25 05:41:43 +00:00
for a given thread model , if the message did not belong to
2011-08-25 12:27:57 +00:00
an existing thread .
The default behavior is to create a new record of the corresponding
2012-09-04 14:50:11 +00:00
model ( based on some very basic info extracted from the message ) .
2011-07-22 16:34:57 +00:00
Additional behavior may be implemented by overriding this method .
: param dict msg_dict : a map containing the email details and
2011-08-23 17:58:09 +00:00
attachments . See ` ` message_process ` ` and
2011-08-25 12:27:57 +00:00
` ` mail . message . parse ` ` for details .
2011-07-22 16:34:57 +00:00
: param dict custom_values : optional dictionary of additional
field values to pass to create ( )
when creating the new thread record .
Be careful , these values may override
any other values coming from the message .
: param dict context : if a ` ` thread_model ` ` value is present
in the context , its value will be used
to determine the model of the record
to create ( instead of the current model ) .
: rtype : int
: return : the id of the newly created thread object
"""
if context is None :
context = { }
2013-01-08 14:20:21 +00:00
data = { }
if isinstance ( custom_values , dict ) :
data = custom_values . copy ( )
2011-07-22 16:34:57 +00:00
model = context . get ( ' thread_model ' ) or self . _name
model_pool = self . pool . get ( model )
fields = model_pool . fields_get ( cr , uid , context = context )
if ' name ' in fields and not data . get ( ' name ' ) :
2012-08-14 08:04:21 +00:00
data [ ' name ' ] = msg_dict . get ( ' subject ' , ' ' )
2011-07-22 16:34:57 +00:00
res_id = model_pool . create ( cr , uid , data , context = context )
return res_id
2012-06-04 14:12:54 +00:00
def message_update ( self , cr , uid , ids , msg_dict , update_vals = None , context = None ) :
2011-08-23 17:58:09 +00:00
""" Called by ``message_process`` when a new message is received
2012-09-04 14:50:11 +00:00
for an existing thread . The default behavior is to update the record
with update_vals taken from the incoming email .
2011-07-22 16:34:57 +00:00
Additional behavior may be implemented by overriding this
method .
: param dict msg_dict : a map containing the email details and
2012-07-05 10:22:19 +00:00
attachments . See ` ` message_process ` ` and
` ` mail . message . parse ( ) ` ` for details .
: param dict update_vals : a dict containing values to update records
2012-06-04 14:12:54 +00:00
given their ids ; if the dict is None or is
void , no write operation is performed .
2011-07-22 16:34:57 +00:00
"""
2012-06-04 14:12:54 +00:00
if update_vals :
self . write ( cr , uid , ids , update_vals , context = context )
2011-07-22 16:34:57 +00:00
return True
2012-08-23 18:54:43 +00:00
def _message_extract_payload ( self , message , save_original = False ) :
""" Extract body as HTML and attachments from the mail message """
attachments = [ ]
body = u ' '
if save_original :
attachments . append ( ( ' original_email.eml ' , message . as_string ( ) ) )
if not message . is_multipart ( ) or ' text/ ' in message . get ( ' content-type ' , ' ' ) :
encoding = message . get_content_charset ( )
body = message . get_payload ( decode = True )
body = tools . ustr ( body , encoding , errors = ' replace ' )
2012-09-05 16:01:45 +00:00
if message . get_content_type ( ) == ' text/plain ' :
# text/plain -> <pre/>
2012-11-14 11:11:29 +00:00
body = tools . append_content_to_html ( u ' ' , body , preserve = True )
2012-08-23 18:54:43 +00:00
else :
alternative = ( message . get_content_type ( ) == ' multipart/alternative ' )
for part in message . walk ( ) :
if part . get_content_maintype ( ) == ' multipart ' :
2012-12-19 11:05:02 +00:00
continue # skip container
filename = part . get_filename ( ) # None if normal part
encoding = part . get_content_charset ( ) # None if attachment
2012-08-23 18:54:43 +00:00
# 1) Explicit Attachments -> attachments
2012-09-05 15:51:21 +00:00
if filename or part . get ( ' content-disposition ' , ' ' ) . strip ( ) . startswith ( ' attachment ' ) :
2012-08-23 18:54:43 +00:00
attachments . append ( ( filename or ' attachment ' , part . get_payload ( decode = True ) ) )
continue
# 2) text/plain -> <pre/>
if part . get_content_type ( ) == ' text/plain ' and ( not alternative or not body ) :
2012-08-31 15:51:03 +00:00
body = tools . append_content_to_html ( body , tools . ustr ( part . get_payload ( decode = True ) ,
2012-11-14 11:11:29 +00:00
encoding , errors = ' replace ' ) , preserve = True )
2012-08-23 18:54:43 +00:00
# 3) text/html -> raw
elif part . get_content_type ( ) == ' text/html ' :
html = tools . ustr ( part . get_payload ( decode = True ) , encoding , errors = ' replace ' )
if alternative :
body = html
2011-09-07 15:13:48 +00:00
else :
2012-08-31 15:51:03 +00:00
body = tools . append_content_to_html ( body , html , plaintext = False )
2012-08-23 18:54:43 +00:00
# 4) Anything else -> attachment
else :
attachments . append ( ( filename or ' attachment ' , part . get_payload ( decode = True ) ) )
return body , attachments
def message_parse ( self , cr , uid , message , save_original = False , context = None ) :
2012-08-16 15:48:23 +00:00
""" Parses a string or email.message.Message representing an
RFC - 2822 email , and returns a generic dict holding the
message details .
2011-07-22 16:34:57 +00:00
2012-08-16 15:48:23 +00:00
: param message : the message to parse
: type message : email . message . Message | string | unicode
: param bool save_original : whether the returned dict
2012-08-23 18:54:43 +00:00
should include an ` ` original ` ` attachment containing
the source of the message
2011-07-22 16:34:57 +00:00
: rtype : dict
2012-08-16 15:48:23 +00:00
: return : A dict with the following structure , where each
field may not be present if missing in original
message : :
2012-10-09 13:40:20 +00:00
{ ' message_id ' : msg_id ,
2012-08-16 15:48:23 +00:00
' subject ' : subject ,
2012-08-23 18:54:43 +00:00
' from ' : from ,
' to ' : to ,
' cc ' : cc ,
2012-08-31 08:01:03 +00:00
' body ' : unified_body ,
2012-08-16 15:48:23 +00:00
' attachments ' : [ ( ' file1 ' , ' bytes ' ) ,
2012-08-23 18:54:43 +00:00
( ' file2 ' , ' bytes ' ) }
2012-08-16 15:48:23 +00:00
}
2011-07-22 16:34:57 +00:00
"""
2012-10-25 11:30:48 +00:00
msg_dict = {
' type ' : ' email ' ,
2012-10-25 13:50:20 +00:00
' author_id ' : False ,
2012-10-25 11:30:48 +00:00
}
2012-08-23 18:54:43 +00:00
if not isinstance ( message , Message ) :
if isinstance ( message , unicode ) :
# Warning: message_from_string doesn't always work correctly on unicode,
# we must use utf-8 strings here :-(
message = message . encode ( ' utf-8 ' )
message = email . message_from_string ( message )
message_id = message [ ' message-id ' ]
2012-08-16 15:48:23 +00:00
if not message_id :
# Very unusual situation, be we should be fault-tolerant here
2012-08-23 18:54:43 +00:00
message_id = " < %s @localhost> " % time . time ( )
_logger . debug ( ' Parsing Message without message-id, generating a random one: %s ' , message_id )
msg_dict [ ' message_id ' ] = message_id
2012-08-16 15:48:23 +00:00
2012-08-23 18:54:43 +00:00
if ' Subject ' in message :
msg_dict [ ' subject ' ] = decode ( message . get ( ' Subject ' ) )
2012-08-16 15:48:23 +00:00
2012-10-25 13:50:20 +00:00
# Envelope fields not stored in mail.message but made available for message_new()
2012-08-23 18:54:43 +00:00
msg_dict [ ' from ' ] = decode ( message . get ( ' from ' ) )
msg_dict [ ' to ' ] = decode ( message . get ( ' to ' ) )
msg_dict [ ' cc ' ] = decode ( message . get ( ' cc ' ) )
2012-08-16 15:48:23 +00:00
2012-08-23 18:54:43 +00:00
if ' From ' in message :
author_ids = self . _message_find_partners ( cr , uid , message , [ ' From ' ] , context = context )
2012-08-16 15:48:23 +00:00
if author_ids :
2012-08-23 18:54:43 +00:00
msg_dict [ ' author_id ' ] = author_ids [ 0 ]
2013-02-21 15:08:49 +00:00
msg_dict [ ' email_from ' ] = decode ( message . get ( ' from ' ) )
2013-02-13 09:59:42 +00:00
partner_ids = self . _message_find_partners ( cr , uid , message , [ ' To ' , ' Cc ' ] , context = context )
2012-11-07 10:51:48 +00:00
msg_dict [ ' partner_ids ' ] = [ ( 4 , partner_id ) for partner_id in partner_ids ]
2012-08-16 15:48:23 +00:00
2012-08-23 18:54:43 +00:00
if ' Date ' in message :
2012-12-18 02:11:23 +00:00
try :
date_hdr = decode ( message . get ( ' Date ' ) )
parsed_date = dateutil . parser . parse ( date_hdr , fuzzy = True )
if parsed_date . utcoffset ( ) is None :
# naive datetime, so we arbitrarily decide to make it
# UTC, there's no better choice. Should not happen,
# as RFC2822 requires timezone offset in Date headers.
stored_date = parsed_date . replace ( tzinfo = pytz . utc )
else :
stored_date = parsed_date . astimezone ( pytz . utc )
except Exception :
_logger . warning ( ' Failed to parse Date header %r in incoming mail '
' with message-id %r , assuming current date/time. ' ,
message . get ( ' Date ' ) , message_id )
stored_date = datetime . datetime . now ( )
msg_dict [ ' date ' ] = stored_date . strftime ( tools . DEFAULT_SERVER_DATETIME_FORMAT )
2012-08-16 15:48:23 +00:00
2012-08-23 18:54:43 +00:00
if ' In-Reply-To ' in message :
2012-09-05 15:51:21 +00:00
parent_ids = self . pool . get ( ' mail.message ' ) . search ( cr , uid , [ ( ' message_id ' , ' = ' , decode ( message [ ' In-Reply-To ' ] ) ) ] )
2012-08-28 17:39:01 +00:00
if parent_ids :
msg_dict [ ' parent_id ' ] = parent_ids [ 0 ]
if ' References ' in message and ' parent_id ' not in msg_dict :
2012-09-05 15:51:21 +00:00
parent_ids = self . pool . get ( ' mail.message ' ) . search ( cr , uid , [ ( ' message_id ' , ' in ' ,
2012-08-28 17:39:01 +00:00
[ x . strip ( ) for x in decode ( message [ ' References ' ] ) . split ( ) ] ) ] )
if parent_ids :
msg_dict [ ' parent_id ' ] = parent_ids [ 0 ]
2012-09-05 15:51:21 +00:00
2012-08-23 18:54:43 +00:00
msg_dict [ ' body ' ] , msg_dict [ ' attachments ' ] = self . _message_extract_payload ( message )
return msg_dict
2012-02-01 16:21:36 +00:00
#------------------------------------------------------
# Note specific
#------------------------------------------------------
2012-04-25 05:41:43 +00:00
2012-04-02 11:50:02 +00:00
def log ( self , cr , uid , id , message , secondary = False , context = None ) :
2012-09-04 13:36:48 +00:00
_logger . warning ( " log() is deprecated. As this module inherit from " \
" mail.thread, the message will be managed by this " \
" module instead of by the res.log mechanism. Please " \
" use mail_thread.message_post() instead of the " \
" now deprecated res.log. " )
2012-08-22 11:34:39 +00:00
self . message_post ( cr , uid , [ id ] , message , context = context )
2013-02-21 18:42:43 +00:00
def _message_add_suggested_recipient ( self , result , obj , partner = None , email = None , reason = ' ' , context = None ) :
if partner and partner in obj . message_follower_ids :
return result
if partner and partner in [ val [ 0 ] for val in result [ obj . id ] ] :
return result
if email and email in [ val [ 1 ] for val in result [ obj . id ] ] :
return result
if partner and partner . email :
result [ obj . id ] . append ( ( partner . id , ' %s < %s > ' % ( partner . name , partner . email ) , reason ) )
elif partner :
result [ obj . id ] . append ( ( partner . id , ' %s ' % ( partner . name ) , reason ) )
else :
result [ obj . id ] . append ( ( False , email , reason ) )
return result
2013-02-20 12:49:23 +00:00
def message_get_suggested_recipients ( self , cr , uid , ids , context = None ) :
2013-02-21 18:42:43 +00:00
result = dict . fromkeys ( ids , list ( ) )
if self . _all_columns . get ( ' user_id ' ) :
for obj in self . browse ( cr , SUPERUSER_ID , ids , context = context ) : # SUPERUSER because of a read on res.users that would crash otherwise IMHO
if not obj . user_id or not obj . user_id . partner_id :
continue
self . _message_add_suggested_recipient ( result , obj , partner = obj . user_id . partner_id , reason = self . _all_columns [ ' user_id ' ] . column . string , context = context )
return result
2013-02-20 12:49:23 +00:00
def message_get_partner_info_from_emails ( self , cr , uid , emails , link_mail = False , context = None ) :
2013-01-08 16:13:32 +00:00
""" Convert a list of emails into a list partner_ids and a list
new_partner_ids . The return value is non conventional because
it is meant to be used by the mail widget .
: return dict : partner_ids and new_partner_ids
"""
mail_message_obj = self . pool . get ( ' mail.message ' )
2013-02-20 12:49:23 +00:00
partner_obj = self . pool . get ( ' res.partner ' )
2013-02-21 18:42:43 +00:00
result = list ( )
2013-01-08 16:13:32 +00:00
for email in emails :
2013-02-21 18:42:43 +00:00
partner_info = { ' full_name ' : email , ' partner_id ' : False }
2013-01-08 16:13:32 +00:00
m = re . search ( r " ((.+?) \ s*<)?([^<>]+@[^<>]+)>? " , email , re . IGNORECASE | re . DOTALL )
2013-02-21 18:42:43 +00:00
if not m :
continue
2013-02-20 12:49:23 +00:00
email_address = m . group ( 3 )
ids = partner_obj . search ( cr , SUPERUSER_ID , [ ( ' email ' , ' = ' , email_address ) ] , context = context )
2013-01-08 16:13:32 +00:00
if ids :
2013-02-21 18:42:43 +00:00
partner_info [ ' partner_id ' ] = ids [ 0 ]
result . append ( partner_info )
2013-01-08 16:13:32 +00:00
# link mail with this from mail to the new partner id
2013-02-20 12:49:23 +00:00
if link_mail and ids :
print email , ids
message_ids = mail_message_obj . search ( cr , SUPERUSER_ID , [
' | ' ,
( ' email_from ' , ' = ' , email ) ,
( ' email_from ' , ' ilike ' , ' < %s > ' % email ) ,
( ' author_id ' , ' = ' , False )
] , context = context )
if message_ids :
# mail_message_obj.write(cr, SUPERUSER_ID, message_ids, {'author_id': ids[0]}, context=context)
print ' found ' , message_ids
2013-02-21 18:42:43 +00:00
return result
2013-01-08 16:13:32 +00:00
2013-02-21 15:08:49 +00:00
def message_post ( self , cr , uid , thread_id , body = ' ' , subject = None ,
content_subtype = ' html ' , type = ' notification ' , subtype = None ,
parent_id = False , attachments = None , context = None , * * kwargs ) :
2012-09-04 13:36:48 +00:00
""" Post a new message in an existing thread, returning the new
2013-02-21 15:08:49 +00:00
mail . message ID .
2013-02-21 13:26:47 +00:00
2012-11-07 15:39:10 +00:00
: param int thread_id : thread ID to post into , or list with one ID ;
if False / 0 , mail . message model will also be set as False
2012-08-31 08:01:03 +00:00
: param str body : body of the message , usually raw HTML that will
be sanitized
2013-02-21 15:08:49 +00:00
: param str type : see mail_message . type field
: param str content_subtype : : if plaintext : convert body into html
: param int parent_id : handle reply to a previous message by adding the
parent partners to the message in case of private discussion
2012-10-08 14:26:54 +00:00
: param tuple ( str , str ) attachments or list id : list of attachment tuples in the form
2012-08-31 08:01:03 +00:00
` ` ( name , content ) ` ` , where content is NOT base64 encoded
2013-02-21 15:08:49 +00:00
Extra keyword arguments will be used as default column values for the
new mail . message record . Special cases :
- attachment_ids : supposed not attached to any document ; attach them
to the related document . Should only be set by Chatter .
: return int : ID of newly created mail . message
2012-08-22 11:34:39 +00:00
"""
2012-11-08 15:25:02 +00:00
if context is None :
context = { }
if attachments is None :
attachments = { }
2012-10-15 13:23:13 +00:00
mail_message = self . pool . get ( ' mail.message ' )
2013-02-21 10:15:15 +00:00
ir_attachment = self . pool . get ( ' ir.attachment ' )
2013-02-14 12:02:57 +00:00
2013-02-21 15:08:49 +00:00
assert ( not thread_id ) or \
isinstance ( thread_id , ( int , long ) ) or \
( isinstance ( thread_id , ( list , tuple ) ) and len ( thread_id ) == 1 ) , \
" Invalid thread_id; should be 0, False, an ID or a list with one ID "
if isinstance ( thread_id , ( list , tuple ) ) :
thread_id = thread_id [ 0 ]
2013-02-14 12:02:57 +00:00
# if we're processing a message directly coming from the gateway, the destination model was
2013-02-21 09:59:06 +00:00
# set in the context.
2013-02-14 12:02:57 +00:00
model = False
if thread_id :
model = context . get ( ' thread_model ' , self . _name ) if self . _name == ' mail.thread ' else self . _name
2012-08-17 10:03:02 +00:00
2013-02-21 13:26:47 +00:00
# 1: Handle content subtype: if plaintext, converto into HTML
if content_subtype == ' plaintext ' :
body = tools . plaintext2html ( body )
# 2: Private message: add recipients (recipients and author of parent message)
partner_ids = set ( kwargs . pop ( ' partner_ids ' , [ ] ) )
if parent_id and model == ' mail.thread ' :
parent_message = mail_message . browse ( cr , uid , parent_id , context = context )
partner_ids | = set ( [ partner . id for partner in parent_message . partner_ids ] )
if parent_message . author_id :
partner_ids . add ( parent_message . author_id . id )
# 3. Attachments
# - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
2013-02-21 10:15:15 +00:00
attachment_ids = kwargs . pop ( ' attachment_ids ' , [ ] ) or [ ] # because we could receive None (some old code sends None)
if attachment_ids :
filtered_attachment_ids = ir_attachment . search ( cr , SUPERUSER_ID , [
( ' res_model ' , ' = ' , ' mail.compose.message ' ) ,
( ' res_id ' , ' = ' , 0 ) ,
( ' create_uid ' , ' = ' , uid ) ,
( ' id ' , ' in ' , attachment_ids ) ] , context = context )
if filtered_attachment_ids :
ir_attachment . write ( cr , SUPERUSER_ID , filtered_attachment_ids , { ' res_model ' : model , ' res_id ' : thread_id } , context = context )
attachment_ids = [ ( 4 , id ) for id in attachment_ids ]
# Handle attachments parameter, that is a dictionary of attachments
2012-08-22 11:34:39 +00:00
for name , content in attachments :
if isinstance ( content , unicode ) :
content = content . encode ( ' utf-8 ' )
2012-08-17 10:03:02 +00:00
data_attach = {
2012-08-22 11:34:39 +00:00
' name ' : name ,
2012-08-23 18:54:43 +00:00
' datas ' : base64 . b64encode ( str ( content ) ) ,
2012-08-22 11:34:39 +00:00
' datas_fname ' : name ,
' description ' : name ,
2013-02-21 15:08:49 +00:00
' res_model ' : model ,
2012-08-23 18:54:43 +00:00
' res_id ' : thread_id ,
2012-08-17 10:03:02 +00:00
}
2012-09-05 15:51:21 +00:00
attachment_ids . append ( ( 0 , 0 , data_attach ) )
2012-08-17 10:03:02 +00:00
2013-02-21 13:26:47 +00:00
# 4: mail.message.subtype
2013-02-21 15:08:49 +00:00
subtype_id = False
2012-10-15 13:23:13 +00:00
if subtype :
2013-02-21 15:08:49 +00:00
if ' . ' not in subtype :
subtype = ' mail. %s ' % subtype
ref = self . pool . get ( ' ir.model.data ' ) . get_object_reference ( cr , uid , * subtype . split ( ' . ' ) )
2012-10-15 13:23:13 +00:00
subtype_id = ref and ref [ 1 ] or False
2013-02-21 13:09:00 +00:00
# automatically subscribe recipients if asked to
2013-02-21 15:08:49 +00:00
if context . get ( ' mail_post_autofollow ' ) and thread_id and partner_ids :
2013-02-21 14:24:17 +00:00
self . message_subscribe ( cr , uid , [ thread_id ] , list ( partner_ids ) , context = context )
2013-02-21 13:09:00 +00:00
2012-10-15 13:23:13 +00:00
# _mail_flat_thread: automatically set free messages to the first posted message
if self . _mail_flat_thread and not parent_id and thread_id :
message_ids = mail_message . search ( cr , uid , [ ' & ' , ( ' res_id ' , ' = ' , thread_id ) , ( ' model ' , ' = ' , model ) ] , context = context , order = " id ASC " , limit = 1 )
parent_id = message_ids and message_ids [ 0 ] or False
2012-10-25 15:11:23 +00:00
# we want to set a parent: force to set the parent_id to the oldest ancestor, to avoid having more than 1 level of thread
elif parent_id :
message_ids = mail_message . search ( cr , SUPERUSER_ID , [ ( ' id ' , ' = ' , parent_id ) , ( ' parent_id ' , ' != ' , False ) ] , context = context )
2012-10-26 12:36:04 +00:00
# avoid loops when finding ancestors
processed_list = [ ]
2012-10-25 15:42:15 +00:00
if message_ids :
message = mail_message . browse ( cr , SUPERUSER_ID , message_ids [ 0 ] , context = context )
2012-10-26 12:36:04 +00:00
while ( message . parent_id and message . parent_id . id not in processed_list ) :
processed_list . append ( message . parent_id . id )
2012-10-25 15:42:15 +00:00
message = message . parent_id
parent_id = message . id
2012-09-25 08:58:09 +00:00
2012-08-22 11:34:39 +00:00
values = kwargs
2012-09-05 15:51:21 +00:00
values . update ( {
2012-09-25 08:58:09 +00:00
' model ' : model ,
2012-08-22 12:49:43 +00:00
' res_id ' : thread_id or False ,
2012-08-17 10:03:02 +00:00
' body ' : body ,
2012-10-01 13:05:30 +00:00
' subject ' : subject or False ,
2012-09-04 13:36:48 +00:00
' type ' : type ,
2012-08-21 10:43:45 +00:00
' parent_id ' : parent_id ,
2012-08-31 08:01:03 +00:00
' attachment_ids ' : attachment_ids ,
2012-09-20 10:17:04 +00:00
' subtype_id ' : subtype_id ,
2013-02-21 14:24:17 +00:00
' partner_ids ' : [ ( 4 , pid ) for pid in partner_ids ] ,
2012-08-17 10:03:02 +00:00
} )
2012-10-03 14:27:12 +00:00
2012-09-20 10:17:04 +00:00
# Avoid warnings about non-existing fields
for x in ( ' from ' , ' to ' , ' cc ' ) :
values . pop ( x , None )
2012-09-25 08:58:09 +00:00
2013-02-21 15:08:49 +00:00
# Create and auto subscribe the author
msg_id = mail_message . create ( cr , uid , values , context = context )
message = mail_message . browse ( cr , uid , msg_id , context = context )
if message . author_id and thread_id and type != ' notification ' :
self . message_subscribe ( cr , uid , [ thread_id ] , [ message . author_id . id ] , context = context )
return msg_id
2012-04-25 05:41:43 +00:00
2012-10-15 13:23:13 +00:00
#------------------------------------------------------
# Followers API
#------------------------------------------------------
2012-09-27 13:48:23 +00:00
2012-10-15 13:34:38 +00:00
def message_get_subscription_data ( self , cr , uid , ids , context = None ) :
2012-10-18 13:06:01 +00:00
""" Wrapper to get subtypes data. """
2012-09-25 08:58:09 +00:00
return self . _get_subscription_data ( cr , uid , ids , None , None , context = context )
2012-09-20 10:17:04 +00:00
def message_subscribe_users ( self , cr , uid , ids , user_ids = None , subtype_ids = None , context = None ) :
2012-08-22 11:03:13 +00:00
""" Wrapper on message_subscribe, using users. If user_ids is not
provided , subscribe uid instead . """
2012-10-15 13:23:13 +00:00
if user_ids is None :
user_ids = [ uid ]
2012-08-22 11:03:13 +00:00
partner_ids = [ user . partner_id . id for user in self . pool . get ( ' res.users ' ) . browse ( cr , uid , user_ids , context = context ) ]
2012-09-20 10:17:04 +00:00
return self . message_subscribe ( cr , uid , ids , partner_ids , subtype_ids = subtype_ids , context = context )
2012-08-16 10:18:48 +00:00
2012-09-20 10:17:04 +00:00
def message_subscribe ( self , cr , uid , ids , partner_ids , subtype_ids = None , context = None ) :
2012-09-12 13:37:11 +00:00
""" Add partners to the records followers. """
2012-12-21 11:00:03 +00:00
user_pid = self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = context ) [ ' partner_id ' ] [ 0 ]
if set ( partner_ids ) == set ( [ user_pid ] ) :
self . check_access_rights ( cr , uid , ' read ' )
else :
self . check_access_rights ( cr , uid , ' write ' )
2012-12-21 11:14:00 +00:00
2012-11-16 07:57:44 +00:00
self . write ( cr , SUPERUSER_ID , ids , { ' message_follower_ids ' : [ ( 4 , pid ) for pid in partner_ids ] } , context = context )
2012-09-20 11:49:47 +00:00
# if subtypes are not specified (and not set to a void list), fetch default ones
if subtype_ids is None :
2012-08-29 06:55:14 +00:00
subtype_obj = self . pool . get ( ' mail.message.subtype ' )
2012-12-12 16:08:04 +00:00
subtype_ids = subtype_obj . search ( cr , uid , [ ( ' default ' , ' = ' , True ) , ' | ' , ( ' res_model ' , ' = ' , self . _name ) , ( ' res_model ' , ' = ' , False ) ] , context = context )
2012-09-20 11:49:47 +00:00
# update the subscriptions
2012-09-20 10:17:04 +00:00
fol_obj = self . pool . get ( ' mail.followers ' )
2012-10-15 13:23:13 +00:00
fol_ids = fol_obj . search ( cr , SUPERUSER_ID , [ ( ' res_model ' , ' = ' , self . _name ) , ( ' res_id ' , ' in ' , ids ) , ( ' partner_id ' , ' in ' , partner_ids ) ] , context = context )
fol_obj . write ( cr , SUPERUSER_ID , fol_ids , { ' subtype_ids ' : [ ( 6 , 0 , subtype_ids ) ] } , context = context )
2012-09-20 10:17:04 +00:00
return True
2012-02-01 16:21:36 +00:00
2012-08-22 11:03:13 +00:00
def message_unsubscribe_users ( self , cr , uid , ids , user_ids = None , context = None ) :
""" Wrapper on message_subscribe, using users. If user_ids is not
provided , unsubscribe uid instead . """
2012-10-15 13:23:13 +00:00
if user_ids is None :
2012-09-20 10:17:04 +00:00
user_ids = [ uid ]
2012-08-22 11:03:13 +00:00
partner_ids = [ user . partner_id . id for user in self . pool . get ( ' res.users ' ) . browse ( cr , uid , user_ids , context = context ) ]
return self . message_unsubscribe ( cr , uid , ids , partner_ids , context = context )
2012-08-15 13:36:43 +00:00
2012-08-22 11:03:13 +00:00
def message_unsubscribe ( self , cr , uid , ids , partner_ids , context = None ) :
2012-09-12 13:37:11 +00:00
""" Remove partners from the records followers. """
2012-12-21 11:00:03 +00:00
user_pid = self . pool . get ( ' res.users ' ) . read ( cr , uid , uid , [ ' partner_id ' ] , context = context ) [ ' partner_id ' ] [ 0 ]
if set ( partner_ids ) == set ( [ user_pid ] ) :
2012-12-20 14:09:46 +00:00
self . check_access_rights ( cr , uid , ' read ' )
else :
self . check_access_rights ( cr , uid , ' write ' )
2012-11-21 13:57:21 +00:00
return self . write ( cr , SUPERUSER_ID , ids , { ' message_follower_ids ' : [ ( 3 , pid ) for pid in partner_ids ] } , context = context )
2012-02-01 16:21:36 +00:00
2013-02-21 18:42:43 +00:00
# def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=['user_id'], context=None):
# """ Returns the list of relational fields linking to res.users that should
# trigger an auto subscribe. The default list checks for the fields
# - called 'user_id'
# - linking to res.users
# - with track_visibility set
# In OpenERP V7, this is sufficent for all major addon such as opportunity,
# project, issue, recruitment, sale.
# Override this method if a custom behavior is needed about fields
# that automatically subscribe users.
# """
# user_field_lst = []
# for name, column_info in self._all_columns.items():
# if name in auto_follow_fields and name in updated_fields and getattr(column_info.column, 'track_visibility', False) and column_info.column._obj == 'res.users':
# user_field_lst.append(name)
# return user_field_lst
2013-01-30 13:27:23 +00:00
def message_auto_subscribe ( self , cr , uid , ids , updated_fields , context = None ) :
2012-12-19 16:42:39 +00:00
"""
1. fetch project subtype related to task ( parent_id . res_model = ' project.task ' )
2. for each project subtype : subscribe the follower to the task
"""
2012-12-18 15:34:57 +00:00
subtype_obj = self . pool . get ( ' mail.message.subtype ' )
follower_obj = self . pool . get ( ' mail.followers ' )
2013-01-30 09:09:36 +00:00
# fetch auto_follow_fields
2013-02-21 18:42:43 +00:00
# user_field_lst = self._message_get_auto_subscribe_fields(cr, uid, updated_fields, context=context)
2013-01-30 09:09:36 +00:00
2012-12-19 16:42:39 +00:00
# fetch related record subtypes
related_subtype_ids = subtype_obj . search ( cr , uid , [ ' | ' , ( ' res_model ' , ' = ' , False ) , ( ' parent_id.res_model ' , ' = ' , self . _name ) ] , context = context )
subtypes = subtype_obj . browse ( cr , uid , related_subtype_ids , context = context )
default_subtypes = [ subtype for subtype in subtypes if subtype . res_model == False ]
related_subtypes = [ subtype for subtype in subtypes if subtype . res_model != False ]
relation_fields = set ( [ subtype . relation_field for subtype in subtypes if subtype . relation_field != False ] )
2013-02-21 18:42:43 +00:00
# if (not related_subtypes or not any(relation in updated_fields for relation in relation_fields)) and not user_field_lst:
if not related_subtypes or not any ( relation in updated_fields for relation in relation_fields ) :
2012-12-19 16:42:39 +00:00
return True
2012-12-18 15:34:57 +00:00
for record in self . browse ( cr , uid , ids , context = context ) :
new_followers = dict ( )
2012-12-19 16:42:39 +00:00
parent_res_id = False
parent_model = False
for subtype in related_subtypes :
if not subtype . relation_field or not subtype . parent_id :
continue
if not subtype . relation_field in self . _columns or not getattr ( record , subtype . relation_field , False ) :
continue
parent_res_id = getattr ( record , subtype . relation_field ) . id
parent_model = subtype . res_model
follower_ids = follower_obj . search ( cr , SUPERUSER_ID , [
( ' res_model ' , ' = ' , parent_model ) ,
( ' res_id ' , ' = ' , parent_res_id ) ,
( ' subtype_ids ' , ' in ' , [ subtype . id ] )
] , context = context )
for follower in follower_obj . browse ( cr , SUPERUSER_ID , follower_ids , context = context ) :
new_followers . setdefault ( follower . partner_id . id , set ( ) ) . add ( subtype . parent_id . id )
2013-01-30 09:09:36 +00:00
if parent_res_id and parent_model :
for subtype in default_subtypes :
follower_ids = follower_obj . search ( cr , SUPERUSER_ID , [
( ' res_model ' , ' = ' , parent_model ) ,
( ' res_id ' , ' = ' , parent_res_id ) ,
( ' subtype_ids ' , ' in ' , [ subtype . id ] )
] , context = context )
for follower in follower_obj . browse ( cr , SUPERUSER_ID , follower_ids , context = context ) :
new_followers . setdefault ( follower . partner_id . id , set ( ) ) . add ( subtype . id )
2013-02-21 18:42:43 +00:00
# # add followers coming from res.users relational fields that are tracked
# user_ids = [getattr(record, name).id for name in user_field_lst if getattr(record, name)]
# for partner_id in [user.partner_id.id for user in self.pool.get('res.users').browse(cr, SUPERUSER_ID, user_ids, context=context)]:
# new_followers.setdefault(partner_id, None)
2012-12-18 15:34:57 +00:00
for pid , subtypes in new_followers . items ( ) :
2013-01-30 09:09:36 +00:00
subtypes = list ( subtypes ) if subtypes is not None else None
self . message_subscribe ( cr , uid , [ record . id ] , [ pid ] , subtypes , context = context )
2012-12-19 16:42:39 +00:00
return True
2012-12-18 13:11:42 +00:00
2012-03-21 17:20:18 +00:00
#------------------------------------------------------
2012-08-28 12:55:22 +00:00
# Thread state
2012-06-04 09:33:24 +00:00
#------------------------------------------------------
2012-06-07 15:17:53 +00:00
2012-08-17 13:34:49 +00:00
def message_mark_as_unread ( self , cr , uid , ids , context = None ) :
2012-08-28 12:55:22 +00:00
""" Set as unread. """
2012-08-17 13:34:49 +00:00
partner_id = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context = context ) . partner_id . id
cr . execute ( '''
2012-09-05 15:51:21 +00:00
UPDATE mail_notification SET
2012-08-17 13:34:49 +00:00
read = false
WHERE
message_id IN ( SELECT id from mail_message where res_id = any ( % s ) and model = % s limit 1 ) and
partner_id = % s
''' , (ids, self._name, partner_id))
return True
2011-07-22 16:34:57 +00:00
2012-06-25 16:13:12 +00:00
def message_mark_as_read ( self , cr , uid , ids , context = None ) :
2012-07-02 15:46:30 +00:00
""" Set as read. """
2012-08-17 13:34:49 +00:00
partner_id = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context = context ) . partner_id . id
2012-08-15 13:36:43 +00:00
cr . execute ( '''
2012-09-05 15:51:21 +00:00
UPDATE mail_notification SET
2012-08-15 13:36:43 +00:00
read = true
2012-08-17 13:34:49 +00:00
WHERE
message_id IN ( SELECT id FROM mail_message WHERE res_id = ANY ( % s ) AND model = % s ) AND
partner_id = % s
''' , (ids, self._name, partner_id))
2012-08-15 13:36:43 +00:00
return True
2012-06-25 16:13:12 +00:00
2011-07-22 16:34:57 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: