##############################################################################
import base64
+from collections import OrderedDict
import datetime
import dateutil
import email
from lxml import etree
import logging
import pytz
+import re
import socket
import time
import xmlrpclib
-import re
from email.message import Message
+from email.utils import formataddr
+from urllib import urlencode
-from openerp import tools
+from openerp import api, tools
from openerp import SUPERUSER_ID
from openerp.addons.mail.mail_message import decode
from openerp.osv import fields, osv, orm
-from openerp.osv.orm import browse_record, browse_null
+from openerp.osv.orm import BaseModel
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
# :param function lambda: returns whether the tracking should record using this subtype
_track = {}
+ # Mass mailing feature
+ _mail_mass_mailing = False
+
def get_empty_list_help(self, cr, uid, help, context=None):
""" Override of BaseModel.get_empty_list_help() to generate an help message
that adds alias information. """
object_id.alias_id.alias_model_id.model == self._name and \
object_id.alias_id.alias_force_thread_id == 0:
alias = object_id.alias_id
- elif catchall_domain and model: # no specific res_id given -> generic help message, take an example alias (i.e. alias of some section_id)
+ if not alias and catchall_domain and model: # no res_id or res_id not linked to an alias -> generic help message, take a generic alias of the model
alias_obj = self.pool.get('mail.alias')
- alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False)], context=context, order='id ASC')
+ alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False), ('alias_parent_thread_id', '=', False)], context=context, order='id ASC')
if alias_ids and len(alias_ids) == 1:
alias = alias_obj.browse(cr, uid, alias_ids[0], context=context)
- message_unread: has uid unread message for the document
- message_summary: html snippet summarizing the Chatter for kanban views """
res = dict((id, dict(message_unread=False, message_unread_count=0, message_summary=' ')) for id in ids)
- user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
+ user_pid = self.pool.get('res.users').read(cr, uid, [uid], ['partner_id'], context=context)[0]['partner_id'][0]
# search for unread messages, directly in SQL to improve performances
cr.execute(""" SELECT m.res_id FROM mail_message m
RIGHT JOIN mail_notification n
- ON (n.message_id = m.id AND n.partner_id = %s AND (n.read = False or n.read IS NULL))
+ ON (n.message_id = m.id AND n.partner_id = %s AND (n.is_read = False or n.is_read IS NULL))
WHERE m.model = %s AND m.res_id in %s""",
(user_pid, self._name, tuple(ids),))
for result in cr.fetchall():
available, which are followed if any """
res = dict((id, dict(message_subtype_data='')) for id in ids)
if user_pid is None:
- user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
+ user_pid = self.pool.get('res.users').read(cr, uid, [uid], ['partner_id'], context=context)[0]['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)
- 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))
+ subtype_ids = subtype_obj.search(
+ cr, uid, [
+ '&', ('hidden', '=', False), '|', ('res_model', '=', self._name), ('res_model', '=', False)
+ ], context=context)
+ subtype_dict = OrderedDict(
+ (subtype.name, {
+ 'default': subtype.default,
+ 'followed': False,
+ 'parent_model': subtype.parent_id and subtype.parent_id.res_model or self._name,
+ '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()
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)])
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]
+ user_pid = self.pool.get('res.users').read(cr, uid, [uid], ['partner_id'], context=context)[0]['partner_id'][0]
for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids):
res[fol.res_id]['message_follower_ids'].append(fol.partner_id.id)
if fol.partner_id.id == user_pid:
auto_join=True,
string='Messages',
help="Messages and communication history"),
+ 'message_last_post': fields.datetime('Last Message Date',
+ help='Date of the last message posted on the record.'),
'message_unread': fields.function(_get_message_data,
fnct_search=_search_message_unread, multi="_get_message_data",
type='boolean', string='Unread Messages',
if context is None:
context = {}
+ if context.get('tracking_disable'):
+ return super(mail_thread, self).create(
+ cr, uid, values, context=context)
+
# subscribe uid unless asked not to
if not context.get('mail_create_nosubscribe'):
pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid).partner_id.id
message_follower_ids = values.get('message_follower_ids') or [] # webclient can send None or False
message_follower_ids.append([4, pid])
values['message_follower_ids'] = message_follower_ids
- # add operation to ignore access rule checking for subscription
- context_operation = dict(context, operation='create')
- else:
- context_operation = context
- thread_id = super(mail_thread, self).create(cr, uid, values, context=context_operation)
+ thread_id = super(mail_thread, self).create(cr, uid, values, context=context)
# automatic logging unless asked not to (mainly for various testing purpose)
if not context.get('mail_create_nolog'):
if not context.get('mail_notrack'):
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
if tracked_fields:
- initial_values = {thread_id: dict((item, False) for item in tracked_fields)}
+ initial_values = {thread_id: dict.fromkeys(tracked_fields, False)}
self.message_track(cr, uid, [thread_id], tracked_fields, initial_values, context=track_ctx)
return thread_id
context = {}
if isinstance(ids, (int, long)):
ids = [ids]
+ if context.get('tracking_disable'):
+ return super(mail_thread, self).write(
+ cr, uid, ids, values, context=context)
# Track initial values of tracked fields
track_ctx = dict(context)
if 'lang' not in track_ctx:
track_ctx['lang'] = self.pool.get('res.users').browse(cr, uid, uid, context=context).lang
- tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
+
+ tracked_fields = None
+ if not context.get('mail_notrack'):
+ tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
+
if tracked_fields:
records = self.browse(cr, uid, ids, context=track_ctx)
- initial_values = dict((this.id, dict((key, getattr(this, key)) for key in tracked_fields.keys())) for this in records)
+ initial_values = dict((record.id, dict((key, getattr(record, key)) for key in tracked_fields))
+ for record in records)
# Perform write, update followers
result = super(mail_thread, self).write(cr, uid, ids, values, context=context)
self.message_auto_subscribe(cr, uid, ids, values.keys(), context=context, values=values)
- if not context.get('mail_notrack'):
- # Perform the tracking
- tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
- else:
- tracked_fields = None
+ # Perform the tracking
if tracked_fields:
self.message_track(cr, uid, ids, tracked_fields, initial_values, context=track_ctx)
+
return result
def unlink(self, cr, uid, ids, context=None):
def copy_data(self, cr, uid, id, default=None, context=None):
# avoid tracking multiple temporary changes during copy
context = dict(context or {}, mail_notrack=True)
-
- default = default or {}
- default['message_ids'] = []
- default['message_follower_ids'] = []
return super(mail_thread, self).copy_data(cr, uid, id, default=default, context=context)
#------------------------------------------------------
:return list: a list of (field_name, column_info obj), containing
always tracked fields and modified on_change fields
"""
- lst = []
+ tracked_fields = []
for name, column_info in self._all_columns.items():
visibility = getattr(column_info.column, 'track_visibility', False)
if visibility == 'always' or (visibility == 'onchange' and name in updated_fields) or name in self._track:
- lst.append(name)
- if not lst:
- return lst
- return self.fields_get(cr, uid, lst, context=context)
+ tracked_fields.append(name)
+
+ if tracked_fields:
+ return self.fields_get(cr, uid, tracked_fields, context=context)
+ return {}
def message_track(self, cr, uid, ids, tracked_fields, initial_values, context=None):
# find subtypes and post messages or log if no subtype found
subtypes = []
- for field, track_info in self._track.items():
- if field not in changes:
- continue
- for subtype, method in track_info.items():
- if method(self, cr, uid, browse_record, context):
- subtypes.append(subtype)
+ # By passing this key, that allows to let the subtype empty and so don't sent email because partners_to_notify from mail_message._notify will be empty
+ if not context.get('mail_track_log_only'):
+ for field, track_info in self._track.items():
+ if field not in changes:
+ continue
+ for subtype, method in track_info.items():
+ if method(self, cr, uid, browse_record, context):
+ subtypes.append(subtype)
posted = False
for subtype in subtypes:
model_obj.check_access_rights(cr, uid, check_operation)
model_obj.check_access_rule(cr, uid, mids, check_operation, context=context)
- def _get_formview_action(self, cr, uid, id, model=None, context=None):
- """ Return an action to open the document. This method is meant to be
- overridden in addons that want to give specific view ids for example.
-
- :param int id: id of the document to open
- :param string model: specific model that overrides self._name
- """
- return {
- 'type': 'ir.actions.act_window',
- 'res_model': model or self._name,
- 'view_type': 'form',
- 'view_mode': 'form',
- 'views': [(False, 'form')],
- 'target': 'current',
- 'res_id': id,
- }
-
def _get_inbox_action_xml_id(self, cr, uid, context=None):
""" When redirecting towards the Inbox, choose which action xml_id has
to be fetched. This method is meant to be inherited, at least in portal
# default action is the Inbox action
self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
act_model, act_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, *self._get_inbox_action_xml_id(cr, uid, context=context))
- action = self.pool.get(act_model).read(cr, uid, act_id, [])
+ action = self.pool.get(act_model).read(cr, uid, [act_id], [])[0]
params = context.get('params')
msg_id = model = res_id = None
if params:
msg_id = params.get('message_id')
model = params.get('model')
- res_id = params.get('res_id')
+ res_id = params.get('res_id', params.get('id')) # signup automatically generated id instead of res_id
if not msg_id and not (model and res_id):
return action
if msg_id and not (model and res_id):
if model_obj.check_access_rights(cr, uid, 'read', raise_exception=False):
try:
model_obj.check_access_rule(cr, uid, [res_id], 'read', context=context)
- if not hasattr(model_obj, '_get_formview_action'):
- action = self.pool.get('mail.thread')._get_formview_action(cr, uid, res_id, model=model, context=context)
- else:
- action = model_obj._get_formview_action(cr, uid, res_id, context=context)
+ action = model_obj.get_access_action(cr, uid, res_id, context=context)
except (osv.except_osv, orm.except_orm):
pass
action.update({
})
return action
+ def _get_access_link(self, cr, uid, mail, partner, context=None):
+ # the parameters to encode for the query and fragment part of url
+ query = {'db': cr.dbname}
+ fragment = {
+ 'login': partner.user_ids[0].login,
+ 'action': 'mail.action_mail_redirect',
+ }
+ if mail.notification:
+ fragment['message_id'] = mail.mail_message_id.id
+ elif mail.model and mail.res_id:
+ fragment.update(model=mail.model, res_id=mail.res_id)
+
+ return "/web?%s#%s" % (urlencode(query), urlencode(fragment))
+
#------------------------------------------------------
# Email specific
#------------------------------------------------------
- def message_get_reply_to(self, cr, uid, ids, context=None):
+ def message_get_default_recipients(self, cr, uid, ids, context=None):
+ if context and context.get('thread_model') and context['thread_model'] in self.pool and context['thread_model'] != self._name:
+ if hasattr(self.pool[context['thread_model']], 'message_get_default_recipients'):
+ sub_ctx = dict(context)
+ sub_ctx.pop('thread_model')
+ return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx)
+ res = {}
+ for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
+ recipient_ids, email_to, email_cc = set(), False, False
+ if 'partner_id' in self._all_columns and record.partner_id:
+ recipient_ids.add(record.partner_id.id)
+ elif 'email_from' in self._all_columns and record.email_from:
+ email_to = record.email_from
+ elif 'email' in self._all_columns:
+ email_to = record.email
+ res[record.id] = {'partner_ids': list(recipient_ids), 'email_to': email_to, 'email_cc': email_cc}
+ return res
+
+ def message_get_reply_to(self, cr, uid, ids, default=None, context=None):
""" Returns the preferred reply-to email address that is basically
the alias of the document, if it exists. """
- if not self._inherits.get('mail.alias'):
- return [False for id in ids]
- 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, SUPERUSER_ID, ids, ['alias_name', 'alias_domain'], context=context)]
+ if context is None:
+ context = {}
+ model_name = context.get('thread_model') or self._name
+ alias_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context)
+ res = dict.fromkeys(ids, False)
+
+ # alias domain: check for aliases and catchall
+ aliases = {}
+ doc_names = {}
+ if alias_domain:
+ if model_name and model_name != 'mail.thread':
+ alias_ids = self.pool['mail.alias'].search(
+ cr, SUPERUSER_ID, [
+ ('alias_parent_model_id.model', '=', model_name),
+ ('alias_parent_thread_id', 'in', ids),
+ ('alias_name', '!=', False)
+ ], context=context)
+ aliases.update(
+ dict((alias.alias_parent_thread_id, '%s@%s' % (alias.alias_name, alias_domain))
+ for alias in self.pool['mail.alias'].browse(cr, SUPERUSER_ID, alias_ids, context=context)))
+ doc_names.update(
+ dict((ng_res[0], ng_res[1])
+ for ng_res in self.pool[model_name].name_get(cr, SUPERUSER_ID, aliases.keys(), context=context)))
+ # left ids: use catchall
+ left_ids = set(ids).difference(set(aliases.keys()))
+ if left_ids:
+ catchall_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.alias", context=context)
+ if catchall_alias:
+ aliases.update(dict((res_id, '%s@%s' % (catchall_alias, alias_domain)) for res_id in left_ids))
+ # compute name of reply-to
+ company_name = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).company_id.name
+ for res_id in aliases.keys():
+ email_name = '%s%s' % (company_name, doc_names.get(res_id) and (' ' + doc_names[res_id]) or '')
+ email_addr = aliases[res_id]
+ res[res_id] = formataddr((email_name, email_addr))
+ left_ids = set(ids).difference(set(aliases.keys()))
+ if left_ids and default:
+ res.update(dict((res_id, default) for res_id in left_ids))
+ return res
+
+ def message_get_email_values(self, cr, uid, id, notif_mail=None, context=None):
+ """ Get specific notification email values to store on the notification
+ mail_mail. Void method, inherit it to add custom values. """
+ res = dict()
+ return res
#------------------------------------------------------
# Mail gateway
s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
return filter(lambda x: x, self._find_partner_from_emails(cr, uid, None, tools.email_split(s), context=context))
- def message_route_verify(self, cr, uid, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, context=None):
+ def message_route_verify(self, cr, uid, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, allow_private=False, context=None):
""" Verify route validity. Check and rules:
1 - if thread_id -> check that document effectively exists; otherwise
fallback on a message_new by resetting thread_id
_create_bounce_email()
return ()
+ if not model and not thread_id and not alias and not allow_private:
+ return ()
+
return (model, thread_id, route[2], route[3], route[4])
def message_route(self, cr, uid, message, message_dict, model=None, thread_id=None,
thread_references = references or in_reply_to
# 1. message is a reply to an existing message (exact match of message_id)
+ ref_match = thread_references and tools.reference_re.search(thread_references)
msg_references = mail_header_msgid_re.findall(thread_references)
mail_message_ids = mail_msg_obj.search(cr, uid, [('message_id', 'in', msg_references)], context=context)
- if mail_message_ids:
+ if ref_match and mail_message_ids:
original_msg = mail_msg_obj.browse(cr, SUPERUSER_ID, mail_message_ids[0], context=context)
model, thread_id = original_msg.model, original_msg.res_id
- _logger.info(
- 'Routing mail from %s to %s with Message-Id %s: direct reply to msg: model: %s, thread_id: %s, custom_values: %s, uid: %s',
- email_from, email_to, message_id, model, thread_id, custom_values, uid)
route = self.message_route_verify(
cr, uid, message, message_dict,
(model, thread_id, custom_values, uid, None),
- update_author=True, assert_model=True, create_fallback=True, context=context)
- return route and [route] or []
+ update_author=True, assert_model=False, create_fallback=True, context=context)
+ if route:
+ _logger.info(
+ 'Routing mail from %s to %s with Message-Id %s: direct reply to msg: model: %s, thread_id: %s, custom_values: %s, uid: %s',
+ email_from, email_to, message_id, model, thread_id, custom_values, uid)
+ return [route]
# 2. message is a reply to an existign thread (6.1 compatibility)
- ref_match = thread_references and tools.reference_re.search(thread_references)
if ref_match:
reply_thread_id = int(ref_match.group(1))
reply_model = ref_match.group(2) or fallback_model
('res_id', '=', thread_id),
], context=context)
if compat_mail_msg_ids and model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'):
- _logger.info(
- 'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s',
- email_from, email_to, message_id, model, thread_id, custom_values, uid)
route = self.message_route_verify(
cr, uid, message, message_dict,
(model, thread_id, custom_values, uid, None),
update_author=True, assert_model=True, create_fallback=True, context=context)
- return route and [route] or []
+ if route:
+ _logger.info(
+ 'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s',
+ email_from, email_to, message_id, model, thread_id, custom_values, uid)
+ return [route]
- # 2. Reply to a private message
+ # 3. Reply to a private message
if in_reply_to:
mail_message_ids = mail_msg_obj.search(cr, uid, [
('message_id', '=', in_reply_to),
], limit=1, context=context)
if mail_message_ids:
mail_message = mail_msg_obj.browse(cr, uid, mail_message_ids[0], context=context)
- _logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
- email_from, email_to, message_id, mail_message.id, custom_values, uid)
route = self.message_route_verify(cr, uid, message, message_dict,
(mail_message.model, mail_message.res_id, custom_values, uid, None),
- update_author=True, assert_model=True, create_fallback=True, context=context)
- return route and [route] or []
-
- # 3. Look for a matching mail.alias entry
+ update_author=True, assert_model=True, create_fallback=True, allow_private=True, context=context)
+ if route:
+ _logger.info(
+ 'Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
+ email_from, email_to, message_id, mail_message.id, custom_values, uid)
+ return [route]
+
+ # 4. 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.
rcpt_tos = \
user_id = uid
_logger.info('No matching user_id for the alias %s', alias.alias_name)
route = (alias.alias_model_id.model, alias.alias_force_thread_id, eval(alias.alias_defaults), user_id, alias)
- _logger.info('Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
- email_from, email_to, message_id, route)
route = self.message_route_verify(cr, uid, message, message_dict, route,
update_author=True, assert_model=True, create_fallback=True, context=context)
if route:
+ _logger.info(
+ 'Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
+ email_from, email_to, message_id, route)
routes.append(route)
return routes
- # 4. Fallback to the provided parameters, if they work
+ # 5. Fallback to the provided parameters, if they work
if not thread_id:
# Legacy: fallback to matching [ID] in the Subject
match = tools.res_re.search(decode_header(message, 'Subject'))
thread_id = int(thread_id)
except:
thread_id = False
- _logger.info('Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
- email_from, email_to, message_id, fallback_model, thread_id, custom_values, uid)
route = self.message_route_verify(cr, uid, message, message_dict,
(fallback_model, thread_id, custom_values, uid, None),
update_author=True, assert_model=True, context=context)
if route:
+ _logger.info(
+ 'Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
+ email_from, email_to, message_id, fallback_model, thread_id, custom_values, uid)
return [route]
- # AssertionError if no routes found and if no bounce occured
+ # ValueError if no routes found and if no bounce occured
raise ValueError(
'No possible route found for incoming message from %s to %s (Message-Id %s:). '
'Create an appropriate mail.alias or force the destination model.' %
def message_route_process(self, cr, uid, message, message_dict, routes, context=None):
# postpone setting message_dict.partner_ids after message_post, to avoid double notifications
+ context = dict(context or {})
partner_ids = message_dict.pop('partner_ids', [])
thread_id = False
for model, thread_id, custom_values, user_id, alias in routes:
if self._name == 'mail.thread':
- context.update({'thread_model': model})
+ context['thread_model'] = model
if model:
model_pool = self.pool[model]
if not (thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new')):
# Note specific
#------------------------------------------------------
- def log(self, cr, uid, id, message, secondary=False, context=None):
- _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.")
- self.message_post(cr, uid, [id], message, context=context)
-
def _message_add_suggested_recipient(self, cr, uid, result, obj, partner=None, email=None, reason='', context=None):
""" Called by message_get_suggested_recipients, to add a suggested
recipient in the result dictionary. The form is :
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
""" Returns suggested recipients for ids. Those are a list of
tuple (partner_id, partner_name, reason), to be managed by Chatter. """
- result = dict.fromkeys(ids, list())
+ result = dict((res_id, []) for res_id in ids)
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
if not obj.user_id or not obj.user_id.partner_id:
m2m_attachment_ids.append((0, 0, data_attach))
return m2m_attachment_ids
+ @api.cr_uid_ids_context
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
subtype=None, parent_id=False, attachments=None, context=None,
content_subtype='html', **kwargs):
# set in the context.
model = False
if thread_id:
- model = context.get('thread_model', self._name) if self._name == 'mail.thread' else self._name
- if model != self._name and hasattr(self.pool[model], 'message_post'):
+ model = context.get('thread_model', False) if self._name == 'mail.thread' else self._name
+ if model and model != self._name and hasattr(self.pool[model], 'message_post'):
del context['thread_model']
return self.pool[model].message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, content_subtype=content_subtype, **kwargs)
if subtype:
if '.' not in subtype:
subtype = 'mail.%s' % subtype
- ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, *subtype.split('.'))
- subtype_id = ref and ref[1] or False
+ subtype_id = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, subtype)
# automatically subscribe recipients if asked to
if context.get('mail_post_autofollow') and thread_id and partner_ids:
self.message_subscribe(cr, uid, [thread_id], list(partner_to_subscribe), context=context)
# _mail_flat_thread: automatically set free messages to the first posted message
- if self._mail_flat_thread and not parent_id and thread_id:
+ if self._mail_flat_thread and model and not parent_id and thread_id:
message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model), ('type', '=', 'email')], context=context, order="id ASC", limit=1)
if not message_ids:
message_ids = message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model)], context=context, order="id ASC", limit=1)
values.update({
'author_id': author_id,
'model': model,
- 'res_id': thread_id or False,
+ 'res_id': model and thread_id or False,
'body': body,
'subject': subject or False,
'type': type,
for x in ('from', 'to', 'cc'):
values.pop(x, None)
- # Create and auto subscribe the author
+ # Post the message
msg_id = mail_message.create(cr, uid, values, context=context)
+
+ # Post-process: subscribe author, update message_last_post
+ if model and model != 'mail.thread' and thread_id and subtype_id:
+ # done with SUPERUSER_ID, because on some models users can post only with read access, not necessarily write access
+ self.write(cr, SUPERUSER_ID, [thread_id], {'message_last_post': fields.datetime.now()}, context=context)
message = mail_message.browse(cr, uid, msg_id, context=context)
- if message.author_id and thread_id and type != 'notification' and not context.get('mail_create_nosubscribe'):
+ if message.author_id and model and thread_id and type != 'notification' and not context.get('mail_create_nosubscribe'):
self.message_subscribe(cr, uid, [thread_id], [message.author_id.id], context=context)
return msg_id
if user_ids is None:
user_ids = [uid]
partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)]
- return self.message_subscribe(cr, uid, ids, partner_ids, subtype_ids=subtype_ids, context=context)
+ result = self.message_subscribe(cr, uid, ids, partner_ids, subtype_ids=subtype_ids, context=context)
+ if partner_ids and result:
+ self.pool['ir.ui.menu'].clear_cache()
+ return result
def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
""" Add partners to the records followers. """
if set(partner_ids) == set([user_pid]):
try:
self.check_access_rights(cr, uid, 'read')
- if context.get('operation', '') == 'create':
- self.check_access_rule(cr, uid, ids, 'create')
- else:
- self.check_access_rule(cr, uid, ids, 'read')
+ self.check_access_rule(cr, uid, ids, 'read')
except (osv.except_osv, orm.except_orm):
return False
else:
if user_ids is None:
user_ids = [uid]
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)
+ result = self.message_unsubscribe(cr, uid, ids, partner_ids, context=context)
+ if partner_ids and result:
+ self.pool['ir.ui.menu'].clear_cache()
+ return result
def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
""" Remove partners from the records followers. """
], context=context)
return fol_obj.unlink(cr, SUPERUSER_ID, fol_ids, context=context)
- def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=['user_id'], context=None):
+ def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=None, 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'
Override this method if a custom behavior is needed about fields
that automatically subscribe users.
"""
+ if auto_follow_fields is None:
+ auto_follow_fields = ['user_id']
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':
record = self.browse(cr, uid, ids[0], context=context)
for updated_field in updated_fields:
field_value = getattr(record, updated_field)
- if isinstance(field_value, browse_record):
+ if isinstance(field_value, BaseModel):
field_value = field_value.id
- elif isinstance(field_value, browse_null):
- field_value = False
values[updated_field] = field_value
# find followers of headers, update structure for new followers
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
cr.execute('''
UPDATE mail_notification SET
- read=false
+ is_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))
+ self.pool.get('mail.notification').invalidate_cache(cr, uid, ['is_read'], context=context)
return True
def message_mark_as_read(self, cr, uid, ids, context=None):
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
cr.execute('''
UPDATE mail_notification SET
- read=true
+ is_read=true
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))
+ self.pool.get('mail.notification').invalidate_cache(cr, uid, ['is_read'], context=context)
return True
#------------------------------------------------------
}
threads.append(data)
return sorted(threads, key=lambda x: (x['popularity'], x['id']), reverse=True)[:3]
+
+ def message_change_thread(self, cr, uid, id, new_res_id, new_model, context=None):
+ """
+ Transfert the list of the mail thread messages from an model to another
+
+ :param id : the old res_id of the mail.message
+ :param new_res_id : the new res_id of the mail.message
+ :param new_model : the name of the new model of the mail.message
+
+ Example : self.pool.get("crm.lead").message_change_thread(self, cr, uid, 2, 4, "project.issue", context)
+ will transfert thread of the lead (id=2) to the issue (id=4)
+ """
+
+ # get the sbtype id of the comment Message
+ subtype_res_id = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'mail.mt_comment', raise_if_not_found=True)
+
+ # get the ids of the comment and none-comment of the thread
+ message_obj = self.pool.get('mail.message')
+ msg_ids_comment = message_obj.search(cr, uid, [
+ ('model', '=', self._name),
+ ('res_id', '=', id),
+ ('subtype_id', '=', subtype_res_id)], context=context)
+ msg_ids_not_comment = message_obj.search(cr, uid, [
+ ('model', '=', self._name),
+ ('res_id', '=', id),
+ ('subtype_id', '!=', subtype_res_id)], context=context)
+
+ # update the messages
+ message_obj.write(cr, uid, msg_ids_comment, {"res_id" : new_res_id, "model" : new_model}, context=context)
+ message_obj.write(cr, uid, msg_ids_not_comment, {"res_id" : new_res_id, "model" : new_model, "subtype_id" : None}, context=context)
+
+ return True