import email
import logging
import pytz
+import re
import time
import xmlrpclib
from email.message import Message
def decode_header(message, header, separator=' '):
- return separator.join(map(decode, message.get_all(header, [])))
+ return separator.join(map(decode, filter(None, message.get_all(header, []))))
class mail_thread(osv.AbstractModel):
# subscribe uid unless asked not to
if not context.get('mail_create_nosubscribe'):
self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
- self.message_subscribe_from_parent(cr, uid, [thread_id], values.keys(), context=context)
+ self.message_auto_subscribe(cr, uid, [thread_id], values.keys(), context=context)
# automatic logging unless asked not to (mainly for various testing purpose)
if not context.get('mail_create_nolog'):
# Perform write, update followers
result = super(mail_thread, self).write(cr, uid, ids, values, context=context)
- self.message_subscribe_from_parent(cr, uid, ids, values.keys(), context=context)
+ self.message_auto_subscribe(cr, uid, ids, values.keys(), context=context)
# Perform the tracking
if tracked_fields:
return []
#------------------------------------------------------
+ # Email specific
+ #------------------------------------------------------
+
+ def message_get_reply_to(self, cr, uid, ids, context=None):
+ 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, uid, ids, ['alias_name', 'alias_domain'], context=context)]
+
+ #------------------------------------------------------
# Mail gateway
#------------------------------------------------------
for alias in mail_alias.browse(cr, uid, alias_ids, context=context):
user_id = alias.alias_user_id.id
if not user_id:
- user_id = self._message_find_user_id(cr, uid, message, context=context)
+ # 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
+ _logger.debug('No matching user_id for the alias %s', alias.alias_name)
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)
"""
if context is None:
context = {}
+ data = {}
+ if isinstance(custom_values, dict):
+ data = custom_values.copy()
model = context.get('thread_model') or self._name
model_pool = self.pool.get(model)
fields = model_pool.fields_get(cr, uid, context=context)
- data = model_pool.default_get(cr, uid, fields, context=context)
if 'name' in fields and not data.get('name'):
data['name'] = msg_dict.get('subject', '')
- if custom_values and isinstance(custom_values, dict):
- data.update(custom_values)
res_id = model_pool.create(cr, uid, data, context=context)
return res_id
msg_dict['author_id'] = author_ids[0]
else:
msg_dict['email_from'] = message.get('from')
- partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
+ partner_ids = self._message_find_partners(cr, uid, message, ['To', 'Cc'], context=context)
msg_dict['partner_ids'] = [(4, partner_id) for partner_id in partner_ids]
if 'Date' in message:
"now deprecated res.log.")
self.message_post(cr, uid, [id], message, context=context)
+ def message_create_partners_from_emails(self, cr, uid, emails, context=None):
+ """ 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
+ """
+ partner_obj = self.pool.get('res.partner')
+ mail_message_obj = self.pool.get('mail.message')
+
+ partner_ids = []
+ new_partner_ids = []
+ for email in emails:
+ m = re.search(r"((.+?)\s*<)?([^<>]+@[^<>]+)>?", email, re.IGNORECASE | re.DOTALL)
+ name = m.group(2) or m.group(0)
+ email = m.group(3)
+ ids = partner_obj.search(cr, SUPERUSER_ID, [('email', '=', email)], context=context)
+ if ids:
+ partner_ids.append(ids[0])
+ partner_id = ids[0]
+ else:
+ partner_id = partner_obj.create(cr, uid, {
+ 'name': name or email,
+ 'email': email,
+ }, context=context)
+ new_partner_ids.append(partner_id)
+
+ # link mail with this from mail to the new partner id
+ 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, {'email_from': None, 'author_id': partner_id}, context=context)
+ return {
+ 'partner_ids': partner_ids,
+ 'new_partner_ids': new_partner_ids,
+ }
+
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
""" Post a new message in an existing thread, returning the new
return mail_message.create(cr, uid, values, context=context)
def message_post_user_api(self, cr, uid, thread_id, body='', parent_id=False,
- attachment_ids=None, extra_emails=None, content_subtype='plaintext',
+ attachment_ids=None, content_subtype='plaintext',
context=None, **kwargs):
""" Wrapper on message_post, used for user input :
- mail gateway
- type and subtype: comment and mail.mt_comment by default
- attachment_ids: supposed not attached to any document; attach them
to the related document. Should only be set by Chatter.
- - extra_email: [ 'Fabien <fpi@openerp.com>', 'al@openerp.com' ]
"""
- partner_obj = self.pool.get('res.partner')
mail_message_obj = self.pool.get('mail.message')
ir_attachment = self.pool.get('ir.attachment')
- extra_emails = extra_emails or []
- # 1.A.1: pre-process partners and incoming extra_emails
+ # 1.A.1: add recipients of parent message (# TDE FIXME HACK: mail.thread -> private message)
partner_ids = set([])
- for email in extra_emails:
- partner_id = partner_obj.find_or_create(cr, uid, email, context=context)
- # link mail with this from mail to the new partner id
- partner_msg_ids = mail_message_obj.search(cr, SUPERUSER_ID, ['|', ('email_from', '=', email), ('email_from', 'ilike', '<%s>' % email), ('author_id', '=', False)], context=context)
- if partner_id and partner_msg_ids:
- mail_message_obj.write(cr, SUPERUSER_ID, partner_msg_ids, {'email_from': None, 'author_id': partner_id}, context=context)
- partner_ids.add((4, partner_id))
- if partner_ids:
- self.message_subscribe(cr, uid, [thread_id], [item[1] for item in partner_ids], context=context)
-
- # 1.A.2: add recipients of parent message
- if parent_id:
+ if parent_id and self._name == 'mail.thread':
parent_message = mail_message_obj.browse(cr, uid, parent_id, context=context)
partner_ids |= set([(4, partner.id) for partner in parent_message.partner_ids])
- # TDE FIXME HACK: mail.thread -> private message
- if self._name == 'mail.thread' and parent_message.author_id.id:
+ if parent_message.author_id.id:
partner_ids.add((4, parent_message.author_id.id))
- # 1.A.3: add specified recipients
- partner_ids |= set(kwargs.pop('partner_ids', []))
+ # 1.A.2: add specified recipients
+ param_partner_ids = set()
+ for item in kwargs.pop('partner_ids', []):
+ if isinstance(item, (list)):
+ param_partner_ids.add((item[0], item[1]))
+ elif isinstance(item, (int, long)):
+ param_partner_ids.add((4, item))
+ else:
+ param_partner_ids.add(item)
+ partner_ids |= param_partner_ids
+
+ # 1.A.3: add parameters recipients as follower
+ # TDE FIXME in 7.1: should check whether this comes from email_list or partner_ids
+ if param_partner_ids and self._name != 'mail.thread':
+ self.message_subscribe(cr, uid, [thread_id], [pid[1] for pid in param_partner_ids], context=context)
# 1.B: handle body, message_type and message_subtype
if content_subtype == 'plaintext':
# 3. Post message
return self.message_post(cr, uid, thread_id=thread_id, body=body,
type=msg_type, subtype=msg_subtype, parent_id=parent_id,
- attachment_ids=attachment_ids, partner_ids=partner_ids, context=context, **kwargs)
+ attachment_ids=attachment_ids, partner_ids=list(partner_ids), context=context, **kwargs)
#------------------------------------------------------
# Followers API
self.check_access_rights(cr, uid, 'write')
return self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
- def message_subscribe_from_parent(self, cr, uid, ids, updated_fields, context=None):
+ 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
+
+ def message_auto_subscribe(self, cr, uid, ids, updated_fields, context=None):
"""
1. fetch project subtype related to task (parent_id.res_model = 'project.task')
2. for each project subtype: subscribe the follower to the task
subtype_obj = self.pool.get('mail.message.subtype')
follower_obj = self.pool.get('mail.followers')
+ # fetch auto_follow_fields
+ user_field_lst = self._message_get_auto_subscribe_fields(cr, uid, updated_fields, context=context)
+
# 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])
- if not related_subtypes or not any(relation in updated_fields for relation in relation_fields):
+ if (not related_subtypes or not any(relation in updated_fields for relation in relation_fields)) and not user_field_lst:
return True
for record in self.browse(cr, uid, ids, 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)
- if not parent_res_id or not parent_model:
- continue
-
- 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)
+ 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)
+
+ # 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)
for pid, subtypes in new_followers.items():
- self.message_subscribe(cr, uid, [record.id], [pid], list(subtypes), context=context)
+ subtypes = list(subtypes) if subtypes is not None else None
+ self.message_subscribe(cr, uid, [record.id], [pid], subtypes, context=context)
return True
#------------------------------------------------------