X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fmail%2Fmail_thread.py;h=12e43d07c8d0b0ef7d5c90a7573830a4d4a0a040;hb=982cfa5d4760892f9507743cb24ccf4e8f18f4f2;hp=6b2a7931bb02b4f17e32bbf77e1f1b9a24b35ac0;hpb=6dc32eb2872f805474a5a5fc809d2799d837b0c5;p=odoo%2Fodoo.git diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 6b2a793..12e43d0 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -20,23 +20,20 @@ ############################################################################## import base64 +import datetime import dateutil import email import logging import pytz import time -from openerp import tools import xmlrpclib - -from mako.template import Template as MakoTemplate - from email.message import Message -from mail_message import decode + +from openerp import tools from openerp import SUPERUSER_ID +from openerp.addons.mail.mail_message import decode from openerp.osv import fields, osv -from openerp.osv.orm import browse_record from openerp.tools.safe_eval import safe_eval as eval -from tools.translate import _ _logger = logging.getLogger(__name__) @@ -73,20 +70,22 @@ class mail_thread(osv.AbstractModel): _description = 'Email Thread' _mail_flat_thread = True - _TRACK_TEMPLATE = """ - %if message_description: - ${message_description} - %endif - - """ + # Automatic logging system if mail installed + # _track = { + # 'field': { + # '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, + # }, + # '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 + _track = {} def _get_message_data(self, cr, uid, ids, name, args, context=None): """ Computes: @@ -231,32 +230,41 @@ class mail_thread(osv.AbstractModel): #------------------------------------------------------ def create(self, cr, uid, values, context=None): - """ Override to subscribe the current user. """ + """ Chatter override : + - subscribe uid + - subscribe followers of parent + - log a creation message + """ if context is None: context = {} thread_id = super(mail_thread, self).create(cr, uid, values, context=context) # subscribe uid unless asked not to - if not context.get('mail_nosubscribe'): + 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], context=context) - - # automatic logging - # self.message_post(cr, uid, thread_id, body='Document created.', context=context) + self.message_subscribe_from_parent(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'): + self.message_post(cr, uid, thread_id, body='Document created', context=context) return thread_id def write(self, cr, uid, ids, values, context=None): + if isinstance(ids, (int, long)): + ids = [ids] + # Track initial values of tracked fields tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=context) - to_log = [name for name in values.keys() if name in tracked_fields] - if to_log: - initial = self.read(cr, uid, ids, [name for name, info in tracked_fields.items()], context=context) + if tracked_fields: + initial = self.read(cr, uid, ids, tracked_fields.keys(), context=context) initial_values = dict((item['id'], item) for item in initial) + # 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) - if to_log: - self.message_track(cr, uid, ids, values.keys(), initial_values, context=context) + # Perform the tracking + if tracked_fields: + self.message_track(cr, uid, ids, tracked_fields, initial_values, context=context) return result def unlink(self, cr, uid, ids, context=None): @@ -290,89 +298,84 @@ class mail_thread(osv.AbstractModel): :return list: a list of (field_name, column_info obj), containing always tracked fields and modified on_change fields """ - return dict((name, column_info) - for name, column_info in self._all_columns.items() - if getattr(column_info.column, '_track_visibility', False) == 2 - or (getattr(column_info.column, '_track_visibility', False) == 1 and name in updated_fields)) - - def message_track(self, cr, uid, ids, updated_fields, initial_values, log_message='Document updated', context=None): - """ - :param list updated_fields: modified field names - """ - translation_obj = self.pool.get('ir.translation') - - def format_false_value(field_obj): - if field_obj._type == 'boolean': - return False - return field_obj._symbol_set[1](False) - - def convert_for_comparison(value, field_obj): + lst = [] + 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) + + def message_track(self, cr, uid, ids, tracked_fields, initial_values, context=None): + + def convert_for_display(value, col_info): + if not value and col_info['type'] == 'boolean': + return 'False' if not value: - return format_false_value(field_obj) - if isinstance(value, browse_record): # compare browse record on id only - return value.id - if isinstance(value, tuple) and len(value) == 2: # name_get result - return value[0] + return '' + if col_info['type'] == 'many2one': + return value[1] + if col_info['type'] == 'selection': + return dict(col_info['selection'])[value] return value - def convert_for_display(value, field_obj): - if not value: - return format_false_value(field_obj) - if field_obj._type == 'many2one': - if isinstance(value, tuple) and len(value) == 2: # already name_get result - return value[1] - if not isinstance(value, browse_record): # value should be an ID - value = self.pool.get(field_obj._obj).browse(cr, SUPERUSER_ID, value, context=None) - return value.name_get()[0][1] - if field_obj._type == 'selection': # CHS/TDE TODO: translated value ? - select_value = filter(lambda item: item[0] == value, field_obj.selection) - return select_value[0][1] - return value - - def translate_field(column_info): - model = column_info.parent_model or self._name - return translation_obj._get_source(cr, uid, '{0},{1}'.format(model, column_info.name), 'field', context.get('lang'), column_info.column.string) - - tracked_fields = self._get_tracked_fields(cr, uid, updated_fields, context=context) - to_log = [name for name in updated_fields if name in tracked_fields] - if not to_log: + def format_message(message_description, tracked_values): + message = '' + if message_description: + message = '%s' % message_description + for name, change in tracked_values.items(): + message += '
    • %s: ' % change.get('col_info') + if change.get('old_value'): + message += '%s → ' % change.get('old_value') + message += '%s
' % change.get('new_value') + return message + + if not tracked_fields: return True - # browse with SUPERUSER_ID to avoid rights issues (i.e. tracking res.partner relational field -> name_get result should always be visible) - for record in self.browse(cr, SUPERUSER_ID, ids, context=context): + for record in self.read(cr, uid, ids, tracked_fields.keys(), context=context): + initial = initial_values[record['id']] + changes = [] tracked_values = {} - default_log = True - changes_found = False - initial = initial_values[record.id] # generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}} for col_name, col_info in tracked_fields.items(): - old_value = convert_for_comparison(initial[col_name], col_info.column) - new_value = convert_for_comparison(record[col_name], col_info.column) - if old_value == new_value and col_info.column._track_visibility == 2: - tracked_values[col_name] = dict(col_info=col_info, new_value=convert_for_display(record[col_name], col_info.column)) - elif old_value != new_value: - tracked_values[col_name] = dict(col_info=col_info, old_value=convert_for_display(initial[col_name], col_info.column), new_value=convert_for_display(record[col_name], col_info.column)) - changes_found = True - if not changes_found: + if record[col_name] == initial[col_name] and getattr(self._all_columns[col_name].column, 'track_visibility', None) == 'always': + tracked_values[col_name] = dict(col_info=col_info['string'], + new_value=convert_for_display(record[col_name], col_info)) + elif record[col_name] != initial[col_name]: + if getattr(self._all_columns[col_name].column, 'track_visibility', None) in ['always', 'onchange']: + tracked_values[col_name] = dict(col_info=col_info['string'], + old_value=convert_for_display(initial[col_name], col_info), + new_value=convert_for_display(record[col_name], col_info)) + if col_name in tracked_fields: + changes.append(col_name) + if not changes: continue # find subtypes and post messages or log if no subtype found - subtypes = set([subtype for field, track_info in self._track.items() if field in to_log - for subtype, method in track_info.items() if method(self, cr, uid, record, context)]) - for subtype in subtypes: - subtype_data = subtype.split('.') - subtype_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, subtype_data[0], subtype_data[1]) - if not subtype_ref: + subtypes = [] + for field, track_info in self._track.items(): + if field not in changes: continue - subtype_rec = self.pool.get('mail.message.subtype').browse(cr, uid, subtype_ref[1], context=context) - message = MakoTemplate(self._TRACK_TEMPLATE).render_unicode(message_description=subtype_rec.description, tracked_values=tracked_values) - self.message_post(cr, uid, record.id, body=message, subtype=subtype, context=context) - default_log = False - if default_log: - message = MakoTemplate(self._TRACK_TEMPLATE).render_unicode(message_description=log_message, tracked_values=tracked_values) - self.message_post(cr, uid, record.id, body=message, context=context) + for subtype, method in track_info.items(): + if method(self, cr, uid, record, context): + subtypes.append(subtype) + posted = False + for subtype in subtypes: + try: + subtype_rec = self.pool.get('ir.model.data').get_object(cr, uid, subtype.split('.')[0], subtype.split('.')[1]) + except ValueError, e: + _logger.debug('subtype %s not found, giving error "%s"' % (subtype, e)) + continue + message = format_message(subtype_rec.description if subtype_rec.description else subtype_rec.name, tracked_values) + self.message_post(cr, uid, record['id'], body=message, subtype=subtype, context=context) + posted = True + if not posted: + message = format_message('', tracked_values) + self.message_post(cr, uid, record['id'], body=message, context=context) return True #------------------------------------------------------ @@ -401,7 +404,7 @@ class mail_thread(osv.AbstractModel): """ 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)]) return [partner_id for email in tools.email_split(s) - for partner_id in self.pool.get('res.partner').search(cr, uid, [('email', 'ilike', email)], context=context)] + for partner_id in self.pool.get('res.partner').search(cr, uid, [('email', 'ilike', email)], limit=1, context=context)] def _message_find_user_id(self, cr, uid, message, context=None): from_local_part = tools.email_split(decode(message.get('From')))[0] @@ -461,13 +464,14 @@ class mail_thread(osv.AbstractModel): message_id, model, thread_id, custom_values, uid) return [(model, thread_id, custom_values, uid)] - # Verify this is a reply to a private message - 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)] + # 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)] # 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 @@ -487,7 +491,13 @@ class mail_thread(osv.AbstractModel): 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('Routing mail with Message-Id %s: direct alias match: %r', message_id, routes) 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) @@ -563,6 +573,10 @@ class mail_thread(osv.AbstractModel): msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context) if strip_attachments: msg.pop('attachments', None) + + # postpone setting msg.partner_ids after message_post, to avoid double notifications + partner_ids = msg.pop('partner_ids', []) + thread_id = False for model, thread_id, custom_values, user_id in routes: if self._name != model: @@ -572,14 +586,28 @@ class mail_thread(osv.AbstractModel): 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) + + # disabled subscriptions during message_new/update to avoid having the system user running the + # email gateway become a follower of all inbound messages + nosub_ctx = dict(context, mail_create_nosubscribe=True) if thread_id and hasattr(model_pool, 'message_update'): - model_pool.message_update(cr, user_id, [thread_id], msg, context=context) + model_pool.message_update(cr, user_id, [thread_id], msg, context=nosub_ctx) else: - thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context) + thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=nosub_ctx) else: assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message." model_pool = self.pool.get('mail.thread') - model_pool.message_post_user_api(cr, uid, [thread_id], context=context, content_subtype='html', **msg) + new_msg_id = model_pool.message_post_user_api(cr, uid, [thread_id], context=context, content_subtype='html', **msg) + + # when posting an incoming email to a document: subscribe the author, if a partner, as follower + if model and thread_id and msg.get('author_id'): + model_pool.message_subscribe(cr, uid, [thread_id], [msg.get('author_id')], context=context) + + 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) + return thread_id def message_new(self, cr, uid, msg_dict, custom_values=None, context=None): @@ -652,9 +680,9 @@ class mail_thread(osv.AbstractModel): alternative = (message.get_content_type() == 'multipart/alternative') for part in message.walk(): if part.get_content_maintype() == 'multipart': - continue # skip container - filename = part.get_filename() # None if normal part - encoding = part.get_content_charset() # None if attachment + continue # skip container + filename = part.get_filename() # None if normal part + encoding = part.get_content_charset() # None if attachment # 1) Explicit Attachments -> attachments if filename or part.get('content-disposition', '').strip().startswith('attachment'): attachments.append((filename or 'attachment', part.get_payload(decode=True))) @@ -736,11 +764,22 @@ class mail_thread(osv.AbstractModel): msg_dict['partner_ids'] = [(4, partner_id) for partner_id in partner_ids] if 'Date' in message: - date_hdr = decode(message.get('Date')) - # convert from email timezone to server timezone - date_server_datetime = dateutil.parser.parse(date_hdr).astimezone(pytz.timezone(tools.get_server_timezone())) - date_server_datetime_str = date_server_datetime.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) - msg_dict['date'] = date_server_datetime_str + 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) if 'In-Reply-To' in message: parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))]) @@ -797,7 +836,7 @@ class mail_thread(osv.AbstractModel): mail_message = self.pool.get('mail.message') model = context.get('thread_model', self._name) if thread_id else False - attachment_ids = [] + attachment_ids = kwargs.pop('attachment_ids', []) for name, content in attachments: if isinstance(content, unicode): content = content.encode('utf-8') @@ -855,8 +894,9 @@ class mail_thread(osv.AbstractModel): return mail_message.create(cr, uid, values, context=context) - def message_post_user_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, - attachment_ids=None, context=None, content_subtype='plaintext', **kwargs): + def message_post_user_api(self, cr, uid, thread_id, body='', parent_id=False, + attachment_ids=None, extra_emails=None, content_subtype='plaintext', + context=None, **kwargs): """ Wrapper on message_post, used for user input : - mail gateway - quick reply in Chatter (refer to mail.js), not @@ -868,30 +908,43 @@ class mail_thread(osv.AbstractModel): - 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 ', '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') - mail_message = self.pool.get('mail.message') - - # 1. Pre-processing: body, partner_ids, type and subtype - if content_subtype == 'plaintext': - body = tools.plaintext2html(body) - - partner_ids = kwargs.pop('partner_ids', []) + extra_emails = extra_emails or [] + + # 1.A.1: pre-process partners and incoming extra_emails + 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), ('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: - parent_message = self.pool.get('mail.message').browse(cr, uid, parent_id, context=context) - partner_ids += [(4, partner.id) for partner in parent_message.partner_ids] + 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: - partner_ids.append((4, parent_message.author_id.id)) + partner_ids.add((4, parent_message.author_id.id)) - message_type = kwargs.pop('type', 'comment') - message_subtype = kwargs.pop('subtype', 'mail.mt_comment') + # 1.A.3: add specified recipients + partner_ids |= set(kwargs.pop('partner_ids', [])) - # 2. Post message - new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=message_type, - subtype=message_subtype, parent_id=parent_id, context=context, partner_ids=partner_ids, **kwargs) + # 1.B: handle body, message_type and message_subtype + if content_subtype == 'plaintext': + body = tools.plaintext2html(body) + msg_type = kwargs.pop('type', 'comment') + msg_subtype = kwargs.pop('subtype', 'mail.mt_comment') - # 3. Post-processing + # 2. Pre-processing: attachments # HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message if attachment_ids: # TDE FIXME (?): when posting a private message, we use mail.thread as a model @@ -907,9 +960,14 @@ class mail_thread(osv.AbstractModel): if filtered_attachment_ids: if thread_id and model: ir_attachment.write(cr, SUPERUSER_ID, attachment_ids, {'res_model': model, 'res_id': thread_id}, context=context) - mail_message.write(cr, SUPERUSER_ID, [new_message_id], {'attachment_ids': [(6, 0, [pid for pid in attachment_ids])]}, context=context) + else: + attachment_ids = [] + attachment_ids = [(4, id) for id in attachment_ids] - return new_message_id + # 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=list(partner_ids), context=context, **kwargs) #------------------------------------------------------ # Followers API @@ -929,7 +987,12 @@ class mail_thread(osv.AbstractModel): def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None): """ Add partners to the records followers. """ - self.check_access_rights(cr, uid, 'read') + 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') + self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context) # if subtypes are not specified (and not set to a void list), fetch default ones if subtype_ids is None: @@ -951,55 +1014,64 @@ class mail_thread(osv.AbstractModel): def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None): """ Remove partners from the records followers. """ - self.check_access_rights(cr, uid, 'read') + 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') 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, context=None): - + def message_subscribe_from_parent(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 record subtypes - subtype_ids = subtype_obj.search(cr, uid, ['|', ('parent_id.res_model', '=', False), ('parent_id.res_model', '=', self._name)], context=context) - if not subtype_ids: - return - subtypes = subtype_obj.browse(cr, uid, subtype_ids, 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): + return True for record in self.browse(cr, uid, ids, context=context): new_followers = dict() - for subtype in subtypes: - if subtype.parent_field and subtype.parent_id: - if subtype.parent_field in self._columns and getattr(record, subtype.parent_field): - parent_res_id = getattr(record, subtype.parent_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) + 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) + + 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) for pid, subtypes in new_followers.items(): self.message_subscribe(cr, uid, [record.id], [pid], list(subtypes), context=context) - - def _subscribe_followers_subtype(self, cr, uid, ids, res_id, model, context=None): - """ TDE note: not the best way to do this, we could override _get_followers - of task, and perform a better mapping of subtypes than a mapping - based on names. - However we will keep this implementation, maybe to be refactored - in 7.1 of future versions. """ - subtype_obj = self.pool.get('mail.message.subtype') - follower_obj = self.pool.get('mail.followers') - # create mapping - subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('res_model', '=', self._name)], context=context) - subtypes = subtype_obj.browse(cr, uid, subtype_ids, context=context) - # fetch subscriptions - follower_ids = follower_obj.search(cr, uid, [('res_model', '=', model), ('res_id', '=', res_id)], context=context) - # copy followers - for follower in follower_obj.browse(cr, uid, follower_ids, context=context): - if not follower.subtype_ids: - continue - subtype_names = [follower_subtype.name for follower_subtype in follower.subtype_ids] - subtype_ids = [subtype.id for subtype in subtypes if subtype.name in subtype_names] - self.message_subscribe(cr, uid, ids, [follower.partner_id.id], - subtype_ids=subtype_ids, context=context) + return True #------------------------------------------------------ # Thread state