def _send_build_result(self, short_message): "Send build result and error message to branch followers" context = { 'subject': short_message, 'build_url': self._get_action_url( **{ 'res_id': self.id, 'model': self._name, 'view_type': 'form', 'action_id': self.env.ref('smile_ci.action_repository_branch_build').id, }), } if self.commit_logs: context['commit_logs'] = tools.plaintext2html(self.commit_logs) if self._context.get('build_error'): context['build_error'] = tools.plaintext2html( self._context['build_error']) template = self.env.ref('smile_ci.mail_template_build_result') subject = self.env['mail.template'].with_context( context).render_template(template.subject, self._name, self.id) body = self.env['mail.template'].with_context(context).render_template( template.body_html, self._name, self.id) self.message_post(body=body, subject=subject, subtype='mail.mt_comment', partner_ids=self.branch_id.partner_ids)
def rating_apply(self, rate, token=None, feedback=None, subtype=None): """ Apply a rating given a token. If the current model inherits from mail.thread mixing, a message is posted on its chatter. :param rate : the rating value to apply :type rate : float :param token : access token :param feedback : additional feedback :type feedback : string :param subtype : subtype for mail :type subtype : string :returns rating.rating record """ Rating, rating = self.env['rating.rating'], None if token: rating = self.env['rating.rating'].search([('access_token', '=', token)], limit=1) else: rating = Rating.search([('res_model', '=', self._name), ('res_id', '=', self.ids[0])], limit=1) if rating: rating.write({'rating': rate, 'feedback': feedback, 'consumed': True}) if hasattr(self, 'message_post'): feedback = tools.plaintext2html(feedback or '') self.message_post( body="<img src='/rating/static/src/img/rating_%s.png' style='width:20px;height:20px;float:left;margin-right: 5px;'/>%s" % (rate, feedback), subtype=subtype or "mail.mt_comment", author_id=rating.partner_id and rating.partner_id.id or None # None will set the default author in mail_thread.py ) if hasattr(self, 'stage_id') and self.stage_id and hasattr(self.stage_id, 'auto_validation_kanban_state') and self.stage_id.auto_validation_kanban_state: if rating.rating > 5: self.write({'kanban_state': 'done'}) if rating.rating < 5: self.write({'kanban_state': 'blocked'}) return rating
def new_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): ''' Overwritten because it is desired to avoid duplicated messages :arguments see the function "message_post" of the file "mail_thread": odoo/addons/mail/mail_thread.py :return int: ID of newly created mail.message OR the id of original message which would be duplicated ''' if context is None: context = {} if not thread_id: raise osv.except_osv(_('Invalid thread_id'), _('Thread ID is not set')) if isinstance(thread_id, (list, tuple)): thread_id = thread_id[0] result = False call_old_message_post_flag = True if attachments is None: # As long as there are any attachments new messages will be created # Otherwise... if context.get('mail_thread_no_duplicate', False): mail_message_obj = self.pool.get('mail.message') # Handle content subtype: if plaintext, convert into HTML if content_subtype == 'plaintext': body_handled = tools.plaintext2html(body) # We need to modify <br/> to <br> since it is saved in this way in the data base, but handled with <br/> in the record body_handled = body_handled.replace("<br/>", "<br>") else: if "<span>" in body: body_handled = "<div>" + HTMLParser.HTMLParser().unescape(body) + "</div>" else: body_handled = HTMLParser.HTMLParser().unescape(body) domain = [('res_id', '=', thread_id), ('model', '=', self._name), ('body', '=', body_handled), ] if not context['mail_thread_no_duplicate']: # If a timedelta is passed, we use it date = datetime.now() - context['mail_thread_no_duplicate'] domain.append(('create_date', '>', date.strftime(DEFAULT_SERVER_DATETIME_FORMAT))) mail_message_id = mail_message_obj.search(cr, uid, domain, context=context, limit=1) if mail_message_id: # Do nothing call_old_message_post_flag = False result = mail_message_id[0] # Put a message in the logger to show that it was ignored on purpose logger.debug("The message: '{0}' has been ignored on purpose since it is duplicated".format(body_handled)) if call_old_message_post_flag: result = old_message_post(self, cr, uid, thread_id, body, subject, type, subtype, parent_id, attachments, context, content_subtype, **kwargs) return result
def test_plaintext2html(self): cases = [ ("First \nSecond \nThird\n \nParagraph\n\r--\nSignature paragraph", 'div', "<div><p>First <br/>Second <br/>Third</p><p>Paragraph</p><p>--<br/>Signature paragraph</p></div>"), ("First<p>It should be escaped</p>\nSignature", False, "<p>First<p>It should be escaped</p><br/>Signature</p>") ] for content, container_tag, expected in cases: html = plaintext2html(content, container_tag) self.assertEqual(html, expected, 'plaintext2html is broken')
def _load_test_logs(self): _logger.info('Importing test logs for build_%s...' % self.id) log_obj = self.env['scm.repository.branch.build.log'] pattern = re.compile(r'([^:]+addons/)(?P<file>[^$]*)') csv_input = cStringIO.StringIO(self._get_logs(TESTFILE)) reader = csv.DictReader(csv_input) for vals in reader: filepath = vals['file'] if filepath: match = pattern.match(filepath) if match: vals['file'] = match.groupdict()['file'] vals['build_id'] = self.id vals['code'] = 'TEST' vals['type'] = 'test' vals['exception'] = tools.plaintext2html(vals['exception']) log_obj.create(vals)
def send_transaction(self, values): res_transaction_id = False if values: ### Validate Data if not values.get('transaction_id'): return { 'faultCode': 0, 'faultString': 'Transaction is required.' } if not values.get('contract_id'): return {'faultCode': 0, 'faultString': 'Contract is required.'} if not values.get('date'): return {'faultCode': 0, 'faultString': 'Date is required.'} ### Find Data From DB contract_id = self.env['res.contract'].search( [('name', '=', values['contract_id']), ('is_template', '=', False), ('type_contract', '=', 'package')], limit=1) if not contract_id: return { 'faultCode': 0, 'faultString': 'contract_id doesn’t exist in Odoo db' } vals = { ### API Fields 'name': values['transaction_id'], 'contract_id': contract_id.id, 'event_ref': values.get('event_ref'), 'date': str(values['date']).strip(), 'user': values.get('user'), } res_transaction_id = self.env['res.transaction'].create(vals) if res_transaction_id: msg = _("Transaction created \n Id: %s \n Event Ref: %s \n User: %s \n Date: %s") % \ (res_transaction_id.name, res_transaction_id.event_ref, res_transaction_id.user, res_transaction_id.date) res_transaction_id.contract_id.message_post( body=tools.plaintext2html(msg)) return {'success': res_transaction_id.id} else: return {'faultCode': 0, 'faultString': 'Something went wrong!'}
def rating_apply(self, rate, token=None, feedback=None, subtype=None): """ Apply a rating given a token. If the current model inherits from mail.thread mixing, a message is posted on its chatter. :param rate : the rating value to apply :type rate : float :param token : access token :param feedback : additional feedback :type feedback : string :param subtype : subtype for mail :type subtype : string :returns rating.rating record """ Rating, rating = self.env['rating.rating'], None if token: rating = self.env['rating.rating'].search( [('access_token', '=', token)], limit=1) else: rating = Rating.search([('res_model', '=', self._name), ('res_id', '=', self.ids[0])], limit=1) if rating: rating.write({ 'rating': rate, 'feedback': feedback, 'consumed': True }) if hasattr(self, 'message_post'): feedback = tools.plaintext2html(feedback or '') self.message_post( body= "<img src='/rating/static/src/img/rating_%s.png' style='width:20px;height:20px;float:left;margin-right: 5px;'/>%s" % (rate, feedback), subtype=subtype or "mail.mt_comment", author_id=rating.partner_id and rating.partner_id.id or None # None will set the default author in mail_thread.py ) if hasattr(self, 'stage_id') and self.stage_id and hasattr( self.stage_id, 'auto_validation_kanban_state' ) and self.stage_id.auto_validation_kanban_state: if rating.rating > 5: self.write({'kanban_state': 'done'}) if rating.rating < 5: self.write({'kanban_state': 'blocked'}) return rating
def cancel_transaction(self, values): transaction_id = False if values: ### Validate Data if not values.get('transaction_id'): return { 'faultCode': 0, 'faultString': 'Transaction is required.' } if not values.get('motif'): return {'faultCode': 0, 'faultString': 'Motif is required.'} if not values.get('date_cancel'): return { 'faultCode': 0, 'faultString': 'Cancel Date is required.' } ### Find Data From DB transaction_id = self.env['res.transaction'].search( [('name', '=', values['transaction_id'])], limit=1) if not transaction_id: return { 'faultCode': 0, 'faultString': 'transaction_id doesn’t exist in Odoo db' } vals = { ### API Fields 'cancel_date': str(values['date_cancel']).strip(), 'motif': values['motif'], } transaction_id.write(vals) transaction_id.action_cancel() if transaction_id: msg = _("Transaction Cancelled \n Id: %s \n Motif: %s \n Date Cancel: %s") % \ (transaction_id.name, transaction_id.motif, transaction_id.cancel_date) transaction_id.contract_id.message_post( body=tools.plaintext2html(msg)) return {'success': transaction_id.id} else: return {'faultCode': 0, 'faultString': 'Something went wrong!'}
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): if context is None: context = {} context = dict(context or {}) #The function is rewritten because of the following functionality if context.get('put_this_subtype_instead', False): subtype = context.get('put_this_subtype_instead') if not context.get('from_composer', False): context['mail_post_autofollow'] = False context['mail_create_nosubscribe'] = True # if attachments is None: attachments = {} mail_message = self.pool.get('mail.message') ir_attachment = self.pool.get('ir.attachment') 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] #0: Find the message's author, because we need it for private discussion author_id = kwargs.get('author_id') if author_id is None: # keep False values author_id = self.pool.get('mail.message')._get_default_author( cr, uid, context=context) user_ids = self.pool.get('res.users').search( cr, uid, [('partner_id', '=', author_id)], context=context) if user_ids: user_obj = self.pool.get('res.users').browse(cr, uid, user_ids[0], context=context) if not context.get('lang') and user_obj.lang: ctx = context.copy() ctx['lang'] = user_obj.lang context = ctx # if we're processing a message directly coming from the gateway, the destination model was # set in the context. model = False if thread_id: 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) # 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) - current author # + legacy-code management (! we manage only 4 and 6 commands) partner_ids = set() kwargs_partner_ids = kwargs.pop('partner_ids', []) for partner_id in kwargs_partner_ids: if isinstance( partner_id, (list, tuple)) and partner_id[0] == 4 and len(partner_id) == 2: partner_ids.add(partner_id[1]) if isinstance( partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3: partner_ids |= set(partner_id[2]) elif isinstance(partner_id, (int, long)): partner_ids.add(partner_id) else: pass # we do not manage anything else if parent_id and not model: parent_message = mail_message.browse(cr, uid, parent_id, context=context) private_followers = set( [partner.id for partner in parent_message.partner_ids]) if parent_message.author_id: private_followers.add(parent_message.author_id.id) private_followers -= set([author_id]) partner_ids |= private_followers # 3. Attachments # - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message attachment_ids = self._message_preprocess_attachments( cr, uid, attachments, kwargs.pop('attachment_ids', []), model, thread_id, context) # 4: mail.message.subtype subtype_id = False if subtype: if '.' not in subtype: subtype = 'mail.%s' % subtype 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: partner_to_subscribe = partner_ids if context.get('mail_post_autofollow_partner_ids'): partner_to_subscribe = filter( lambda item: item in context.get( 'mail_post_autofollow_partner_ids'), 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 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) parent_id = message_ids and message_ids[0] or False # 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) # avoid loops when finding ancestors processed_list = [] if message_ids: message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context) while (message.parent_id and message.parent_id.id not in processed_list): processed_list.append(message.parent_id.id) message = message.parent_id parent_id = message.id values = kwargs values.update({ 'author_id': author_id, 'model': model, 'res_id': model and thread_id or False, 'body': body, 'subject': subject or False, 'type': type, 'parent_id': parent_id, 'attachment_ids': attachment_ids, 'subtype_id': subtype_id, 'partner_ids': [(4, pid) for pid in partner_ids], }) # Avoid warnings about non-existing fields for x in ('from', 'to', 'cc'): values.pop(x, None) # 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 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
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): """ Post a new message in an existing thread, returning the new mail.message ID. :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 :param str body: body of the message, usually raw HTML that will be sanitized :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 :param tuple(str,str) attachments or list id: list of attachment tuples in the form ``(name,content)``, where content is NOT base64 encoded 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 """ if context is None: context = {} if attachments is None: attachments = {} mail_message = self.pool.get('mail.message') ir_attachment = self.pool.get('ir.attachment') 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] # if we're processing a message directly coming from the gateway, the destination model was # set in the context. model = False if thread_id: 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) #0: Find the message's author, because we need it for private discussion author_id = kwargs.get('author_id') if author_id is None: # keep False values author_id = self.pool.get('mail.message')._get_default_author( cr, uid, context=context) # 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) - current author # + legacy-code management (! we manage only 4 and 6 commands) partner_ids = set() kwargs_partner_ids = kwargs.pop('partner_ids', []) for partner_id in kwargs_partner_ids: if isinstance( partner_id, (list, tuple)) and partner_id[0] == 4 and len(partner_id) == 2: partner_ids.add(partner_id[1]) if isinstance( partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3: partner_ids |= set(partner_id[2]) elif isinstance(partner_id, (int, long)): partner_ids.add(partner_id) else: pass # we do not manage anything else if parent_id and not model: parent_message = mail_message.browse(cr, uid, parent_id, context=context) private_followers = set( [partner.id for partner in parent_message.partner_ids]) if parent_message.author_id: private_followers.add(parent_message.author_id.id) private_followers -= set([author_id]) partner_ids |= private_followers # 3. Attachments # - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message attachment_ids = self._message_preprocess_attachments( cr, uid, attachments, kwargs.pop('attachment_ids', []), model, thread_id, context) # 4: mail.message.subtype subtype_id = False if subtype: if '.' not in subtype: subtype = 'mail.%s' % subtype 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: # partner_to_subscribe = partner_ids # if context.get('mail_post_autofollow_partner_ids'): # partner_to_subscribe = filter(lambda item: item in context.get('mail_post_autofollow_partner_ids'), 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 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) parent_id = message_ids and message_ids[0] or False # 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) # avoid loops when finding ancestors processed_list = [] if message_ids: message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context) while (message.parent_id and message.parent_id.id not in processed_list): processed_list.append(message.parent_id.id) message = message.parent_id parent_id = message.id values = kwargs values.update({ 'author_id': author_id, 'model': model, 'res_id': model and thread_id or False, 'body': body, 'subject': subject or False, 'type': type, 'parent_id': parent_id, 'attachment_ids': attachment_ids, 'subtype_id': subtype_id, 'partner_ids': [(4, pid) for pid in partner_ids], }) # Avoid warnings about non-existing fields for x in ('from', 'to', 'cc'): values.pop(x, None) # 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 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
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): """ Post a new message in an existing thread, returning the new mail.message ID. :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 :param str body: body of the message, usually raw HTML that will be sanitized :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 :param tuple(str,str) attachments or list id: list of attachment tuples in the form ``(name,content)``, where content is NOT base64 encoded 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 """ if context is None: context = {} if attachments is None: attachments = {} mail_message = self.pool.get('mail.message') ir_attachment = self.pool.get('ir.attachment') 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] # if we're processing a message directly coming from the gateway, the destination model was # 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: del context['thread_model'] return self.pool.get(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) # 0: Parse email-from, try to find a better author_id based on document's followers for incoming emails email_from = kwargs.get('email_from') if email_from and thread_id and type == 'email' and kwargs.get('author_id'): email_list = tools.email_split(email_from) doc = self.browse(cr, uid, thread_id, context=context) if email_list and doc: author_ids = self.pool.get('res.partner').search(cr, uid, [ ('email', 'ilike', email_list[0]), ('id', 'in', [f.id for f in doc.message_follower_ids]) ], limit=1, context=context) if author_ids: kwargs['author_id'] = author_ids[0] author_id = kwargs.get('author_id') if author_id is None: # keep False values author_id = self.pool.get('mail.message')._get_default_author(cr, uid, context=context) # 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) - current author # + legacy-code management (! we manage only 4 and 6 commands) partner_ids = set() kwargs_partner_ids = kwargs.pop('partner_ids', []) for partner_id in kwargs_partner_ids: if isinstance(partner_id, (list, tuple)) and partner_id[0] == 4 and len(partner_id) == 2: partner_ids.add(partner_id[1]) if isinstance(partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3: partner_ids |= set(partner_id[2]) elif isinstance(partner_id, (int, long)): partner_ids.add(partner_id) else: pass # we do not manage anything else email_cc_ids = set() kwargs_email_cc_ids = kwargs.pop('email_cc_ids', []) for email_cc_id in kwargs_email_cc_ids: if isinstance(partner_id, (list, tuple)) and email_cc_id[0] == 4 and len(email_cc_id) == 2: email_cc_ids.add(email_cc_id[1]) if isinstance(email_cc_id, (list, tuple)) and email_cc_id[0] == 6 and len(email_cc_id) == 3: email_cc_ids |= set(email_cc_id[2]) elif isinstance(email_cc_id, (int, long)): email_cc_ids.add(email_cc_id) else: pass # we do not manage anything else email_bcc_ids = set() kwargs_email_bcc_ids = kwargs.pop('email_bcc_ids', []) for email_bcc_id in kwargs_email_bcc_ids: if isinstance(partner_id, (list, tuple)) and email_bcc_id[0] == 4 and len(email_bcc_id) == 2: email_bcc_ids.add(email_bcc_id[1]) if isinstance(email_bcc_id, (list, tuple)) and email_bcc_id[0] == 6 and len(email_bcc_id) == 3: email_bcc_ids |= set(email_bcc_id[2]) elif isinstance(email_bcc_id, (int, long)): email_bcc_ids.add(email_bcc_id) else: pass # we do not manage anything else if parent_id and not model: parent_message = mail_message.browse(cr, uid, parent_id, context=context) private_followers = set([partner.id for partner in parent_message.partner_ids]) if parent_message.author_id: private_followers.add(parent_message.author_id.id) private_followers -= set([author_id]) partner_ids |= private_followers # 3. Attachments # - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message 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'), ('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 for name, content in attachments: if isinstance(content, unicode): content = content.encode('utf-8') data_attach = { 'name': name, 'datas': base64.b64encode(str(content)), 'datas_fname': name, 'description': name, 'res_model': model, 'res_id': thread_id, } attachment_ids.append((0, 0, data_attach)) # 4: mail.message.subtype subtype_id = False 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 # automatically subscribe recipients if asked to if context.get('mail_post_autofollow') and thread_id and partner_ids: partner_to_subscribe = partner_ids if context.get('mail_post_autofollow_partner_ids'): partner_to_subscribe = filter(lambda item: item in context.get('mail_post_autofollow_partner_ids'), 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: 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 # 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) # avoid loops when finding ancestors processed_list = [] if message_ids: message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context) while (message.parent_id and message.parent_id.id not in processed_list): processed_list.append(message.parent_id.id) message = message.parent_id parent_id = message.id values = kwargs values.update({ 'author_id': author_id, 'model': model, 'res_id': thread_id or False, 'body': body, 'subject': subject or False, 'type': type, 'parent_id': parent_id, 'attachment_ids': attachment_ids, 'subtype_id': subtype_id, 'partner_ids': [(4, pid) for pid in partner_ids], 'email_cc_ids':[(4, ccid) for ccid in email_cc_ids], 'email_bcc_ids':[(4, bccid) for bccid in email_bcc_ids], }) # Avoid warnings about non-existing fields for x in ('from', 'to', 'cc'): values.pop(x, None) # 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' and not context.get('mail_create_nosubscribe'): self.message_subscribe(cr, uid, [thread_id], [message.author_id.id], context=context) return msg_id
def message_custom_post(self, cr, uid, thread_id, body='', subject=None, type='notification', subtype=None, parent_id=False, attachments=None, model=None, res_id=None, context=None, content_subtype='html', **kwargs): """ Post a new message in an existing thread, returning the new mail.message ID. :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 :param str body: body of the message, usually raw HTML that will be sanitized :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 :param tuple(str,str) attachments or list id: list of attachment tuples in the form ``(name,content)``, where content is NOT base64 encoded 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 """ if context is None: context = {} if attachments is None: attachments = {} mail_message = self.pool.get('mail.message') ir_attachment = self.pool.get('ir.attachment') 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] # if we're processing a message directly coming from the gateway, the destination model was # set in the context. if not model: model = 'mail.thread' #0: Find the message's author, because we need it for private discussion author_id = kwargs.get('author_id') if author_id is None: # keep False values author_id = self.pool.get('mail.message')._get_default_author(cr, uid, context=context) # 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) - current author # + legacy-code management (! we manage only 4 and 6 commands) partner_ids = set() kwargs_partner_ids = kwargs.pop('partner_ids', []) for partner_id in kwargs_partner_ids: if isinstance(partner_id, (list, tuple)) and partner_id[0] == 4 and len(partner_id) == 2: partner_ids.add(partner_id[1]) if isinstance(partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3: partner_ids |= set(partner_id[2]) elif isinstance(partner_id, (int, long)): partner_ids.add(partner_id) else: pass # we do not manage anything else if parent_id and not model: parent_message = mail_message.browse(cr, uid, parent_id, context=context) private_followers = set([partner.id for partner in parent_message.partner_ids]) if parent_message.author_id: private_followers.add(parent_message.author_id.id) private_followers -= set([author_id]) partner_ids |= private_followers # 3. Attachments # - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message attachment_ids = self._message_preprocess_attachments(cr, uid, attachments, kwargs.pop('attachment_ids', []), model, thread_id, context) # 4: mail.message.subtype subtype_id = False if subtype: if '.' not in subtype: subtype = 'mail.%s' % subtype 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: partner_to_subscribe = partner_ids if context.get('mail_post_autofollow_partner_ids'): partner_to_subscribe = filter(lambda item: item in context.get('mail_post_autofollow_partner_ids'), 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: 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 # 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) # avoid loops when finding ancestors processed_list = [] if message_ids: message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context) while (message.parent_id and message.parent_id.id not in processed_list): processed_list.append(message.parent_id.id) message = message.parent_id parent_id = message.id values = kwargs values.update({ 'author_id': author_id, 'model': model, 'res_id': res_id or False, 'body': body, 'subject': subject or False, 'type': type, 'parent_id': parent_id, 'attachment_ids': attachment_ids, 'subtype_id': subtype_id, 'partner_ids': [(4, pid) for pid in partner_ids], }) # Avoid warnings about non-existing fields for x in ('from', 'to', 'cc'): values.pop(x, None) # 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'): self.message_subscribe(cr, uid, [thread_id], [message.author_id.id], context=context) return msg_id
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): """ Post a new message in an existing thread, returning the new mail.message ID. :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 :param str body: body of the message, usually raw HTML that will be sanitized :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 :param tuple(str,str) attachments or list id: list of attachment tuples in the form ``(name,content)``, where content is NOT base64 encoded 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 """ if context is None: context = {} if attachments is None: attachments = {} mail_message = self.pool.get('mail.message') ir_attachment = self.pool.get('ir.attachment') 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] # if we're processing a message directly coming from the gateway, the destination model was # 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: del context['thread_model'] return self.pool.get(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) # 0: Parse email-from, try to find a better author_id based on document's followers for incoming emails email_from = kwargs.get('email_from') if email_from and thread_id and type == 'email' and kwargs.get( 'author_id'): email_list = tools.email_split(email_from) doc = self.browse(cr, uid, thread_id, context=context) if email_list and doc: author_ids = self.pool.get('res.partner').search( cr, uid, [('email', 'ilike', email_list[0]), ('id', 'in', [f.id for f in doc.message_follower_ids])], limit=1, context=context) if author_ids: kwargs['author_id'] = author_ids[0] author_id = kwargs.get('author_id') if author_id is None: # keep False values author_id = self.pool.get('mail.message')._get_default_author( cr, uid, context=context) # 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) - current author # + legacy-code management (! we manage only 4 and 6 commands) partner_ids = set() kwargs_partner_ids = kwargs.pop('partner_ids', []) for partner_id in kwargs_partner_ids: if isinstance( partner_id, (list, tuple)) and partner_id[0] == 4 and len(partner_id) == 2: partner_ids.add(partner_id[1]) if isinstance( partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3: partner_ids |= set(partner_id[2]) elif isinstance(partner_id, (int, long)): partner_ids.add(partner_id) else: pass # we do not manage anything else email_cc_ids = set() kwargs_email_cc_ids = kwargs.pop('email_cc_ids', []) for email_cc_id in kwargs_email_cc_ids: if isinstance( partner_id, (list, tuple)) and email_cc_id[0] == 4 and len(email_cc_id) == 2: email_cc_ids.add(email_cc_id[1]) if isinstance( email_cc_id, (list, tuple)) and email_cc_id[0] == 6 and len(email_cc_id) == 3: email_cc_ids |= set(email_cc_id[2]) elif isinstance(email_cc_id, (int, long)): email_cc_ids.add(email_cc_id) else: pass # we do not manage anything else email_bcc_ids = set() kwargs_email_bcc_ids = kwargs.pop('email_bcc_ids', []) for email_bcc_id in kwargs_email_bcc_ids: if isinstance( partner_id, (list, tuple)) and email_bcc_id[0] == 4 and len(email_bcc_id) == 2: email_bcc_ids.add(email_bcc_id[1]) if isinstance( email_bcc_id, (list, tuple)) and email_bcc_id[0] == 6 and len(email_bcc_id) == 3: email_bcc_ids |= set(email_bcc_id[2]) elif isinstance(email_bcc_id, (int, long)): email_bcc_ids.add(email_bcc_id) else: pass # we do not manage anything else if parent_id and not model: parent_message = mail_message.browse(cr, uid, parent_id, context=context) private_followers = set( [partner.id for partner in parent_message.partner_ids]) if parent_message.author_id: private_followers.add(parent_message.author_id.id) private_followers -= set([author_id]) partner_ids |= private_followers # 3. Attachments # - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message 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'), ('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 for name, content in attachments: if isinstance(content, unicode): content = content.encode('utf-8') data_attach = { 'name': name, 'datas': base64.b64encode(str(content)), 'datas_fname': name, 'description': name, 'res_model': model, 'res_id': thread_id, } attachment_ids.append((0, 0, data_attach)) # 4: mail.message.subtype subtype_id = False 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 # automatically subscribe recipients if asked to if context.get('mail_post_autofollow') and thread_id and partner_ids: partner_to_subscribe = partner_ids if context.get('mail_post_autofollow_partner_ids'): partner_to_subscribe = filter( lambda item: item in context.get( 'mail_post_autofollow_partner_ids'), 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: 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 # 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) # avoid loops when finding ancestors processed_list = [] if message_ids: message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context) while (message.parent_id and message.parent_id.id not in processed_list): processed_list.append(message.parent_id.id) message = message.parent_id parent_id = message.id values = kwargs values.update({ 'author_id': author_id, 'model': model, 'res_id': thread_id or False, 'body': body, 'subject': subject or False, 'type': type, 'parent_id': parent_id, 'attachment_ids': attachment_ids, 'subtype_id': subtype_id, 'partner_ids': [(4, pid) for pid in partner_ids], 'email_cc_ids': [(4, ccid) for ccid in email_cc_ids], 'email_bcc_ids': [(4, bccid) for bccid in email_bcc_ids], }) # Avoid warnings about non-existing fields for x in ('from', 'to', 'cc'): values.pop(x, None) # 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' and not context.get( 'mail_create_nosubscribe'): self.message_subscribe(cr, uid, [thread_id], [message.author_id.id], context=context) return msg_id
def message_custom_post(self, body='', subject=None, message_type='notification', subtype=None, parent_id=False, attachments=None, model=None, res_id=None, content_subtype='html', **kwargs): """ Post a new message in an existing thread, returning the new mail.message ID. :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 :param str body: body of the message, usually raw HTML that will be sanitized :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 :param tuple(str,str) attachments or list id: list of attachment tuples in the form ``(name,content)``, where content is NOT base64 encoded: :param str model, model to link the title :param str res_id, id for the actual model sent 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 """ if attachments is None: attachments = {} if self.ids and not self.ensure_one(): raise exceptions.Warning( _('Invalid record set: should be called as model (without records) or on single-record recordset' )) # 0: Find the message's author, because we need it for private discussion author_id = kwargs.get('author_id') if author_id is None: # keep False values author_id = self.env['mail.message']._get_default_author().id # 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) - current author # + legacy-code management (! we manage only 4 and 6 commands) partner_ids = set() kwargs_partner_ids = kwargs.pop('partner_ids', []) for partner_id in kwargs_partner_ids: if isinstance( partner_id, (list, tuple)) and partner_id[0] == 4 and len(partner_id) == 2: partner_ids.add(partner_id[1]) if isinstance( partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3: partner_ids |= set(partner_id[2]) elif isinstance(partner_id, (int, long)): partner_ids.add(partner_id) else: pass # we do not manage anything else if parent_id and not model: parent_message = self.env['mail.message'].browse(parent_id) private_followers = set( [partner.id for partner in parent_message.partner_ids]) if parent_message.author_id: private_followers.add(parent_message.author_id.id) private_followers -= set([author_id]) partner_ids |= private_followers # 3. Attachments # - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message attachment_ids = self._message_preprocess_attachments( attachments, kwargs.pop('attachment_ids', []), model, self.ids and self.ids[0] or None) # 4: mail.message.subtype subtype_id = kwargs.get('subtype_id', False) if not subtype_id: subtype = subtype or 'mt_note' if '.' not in subtype: subtype = 'mail.%s' % subtype subtype_id = self.env['ir.model.data'].xmlid_to_res_id(subtype) # automatically subscribe recipients if asked to if self._context.get( 'mail_post_autofollow') and self.ids and partner_ids: partner_to_subscribe = partner_ids if self._context.get('mail_post_autofollow_partner_ids'): partner_to_subscribe = filter( lambda item: item in self._context.get( 'mail_post_autofollow_partner_ids'), partner_ids) self.message_subscribe(list(partner_to_subscribe), force=False) # _mail_flat_thread: automatically set free messages to the first posted message MailMessage = self.env['mail.message'] if self._mail_flat_thread and model and not parent_id and self.ids: messages = MailMessage.search([ '&', ('res_id', '=', self.ids[0]), ('model', '=', model), ('message_type', '=', 'email') ], order="id ASC", limit=1) if not messages: messages = MailMessage.search( ['&', ('res_id', '=', self.ids[0]), ('model', '=', model)], order="id ASC", limit=1) parent_id = messages and messages[0].id or False # 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: messages = MailMessage.sudo().search([('id', '=', parent_id), ('parent_id', '!=', False)], limit=1) # avoid loops when finding ancestors processed_list = [] if messages: message = messages[0] while (message.parent_id and message.parent_id.id not in processed_list): processed_list.append(message.parent_id.id) message = message.parent_id parent_id = message.id values = kwargs values.update({ 'author_id': author_id, 'model': model, 'res_id': res_id or False, 'body': body, 'subject': subject or False, 'message_type': message_type, 'parent_id': parent_id, 'attachment_ids': attachment_ids, 'subtype_id': subtype_id, 'partner_ids': [(4, pid) for pid in partner_ids], }) # Avoid warnings about non-existing fields for x in ('from', 'to', 'cc'): values.pop(x, None) # Post the message new_message = MailMessage.create(values) # Post-process: subscribe author, update message_last_post # Note: the message_last_post mechanism is no longer used. This # will be removed in a later version. if (self._context.get('mail_save_message_last_post') and model and model != 'mail.thread' and self.ids and subtype_id): subtype_rec = self.env['mail.message.subtype'].sudo().browse( subtype_id) if not subtype_rec.internal: # done with SUPERUSER_ID, because on some models users can post only with read access, not necessarily write access self.sudo().write({'message_last_post': fields.Datetime.now()}) if new_message.author_id and model and self.ids and message_type != 'notification' and not self._context.get( 'mail_create_nosubscribe'): self.message_subscribe([new_message.author_id.id], force=False) return new_message