From cdfe479e2e92b8bfc4e4d5bb32c1db8ad5c431d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 3 Jan 2018 12:39:08 +0100 Subject: [PATCH] [MOV] mail: reorder a bit mail thread methods by category As this class has grown a lot some methods have been put a bit anywhere in the file. To prepare future improvements it is easier to regroup them a bit. remove_accent from mail_alias is also moved to tools in order to separate tools from business or class specific code. --- addons/mail/models/mail_alias.py | 13 +- addons/mail/models/mail_thread.py | 234 +++++++++++++++--------------- odoo/tools/misc.py | 15 ++ 3 files changed, 131 insertions(+), 131 deletions(-) diff --git a/addons/mail/models/mail_alias.py b/addons/mail/models/mail_alias.py index dc09f3be13b..2594d3162e0 100644 --- a/addons/mail/models/mail_alias.py +++ b/addons/mail/models/mail_alias.py @@ -3,26 +3,15 @@ import logging import re -import unicodedata from odoo import _, api, fields, models from odoo.exceptions import ValidationError -from odoo.tools import ustr +from odoo.tools import remove_accents from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) -# Inspired by http://stackoverflow.com/questions/517923 -def remove_accents(input_str): - """Suboptimal-but-better-than-nothing way to replace accented - latin letters by an ASCII equivalent. Will obviously change the - meaning of input_str and work only for some cases""" - input_str = ustr(input_str) - nkfd_form = unicodedata.normalize('NFKD', input_str) - return u''.join([c for c in nkfd_form if not unicodedata.combining(c)]) - - class Alias(models.Model): """A Mail Alias is a mapping of an email address with a given Odoo Document model. It is used by Odoo's mail gateway when processing incoming emails diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 1131f1fe570..b0e15d1a106 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -298,10 +298,6 @@ class MailThread(models.AbstractModel): # avoid tracking multiple temporary changes during copy return super(MailThread, self.with_context(mail_notrack=True)).copy_data(default=default) - # ------------------------------------------------------ - # Technical methods (to clean / move to controllers ?) - # ------------------------------------------------------ - @api.model def get_empty_list_help(self, help): """ Override of BaseModel.get_empty_list_help() to generate an help message @@ -372,6 +368,89 @@ class MailThread(models.AbstractModel): res['arch'] = etree.tostring(doc, encoding='unicode') return res + # ------------------------------------------------------ + # Technical methods / wrappers / tools + # ------------------------------------------------------ + + @api.model + def _garbage_collect_attachments(self): + """ Garbage collect lost mail attachments. Those are attachments + - linked to res_model 'mail.compose.message', the composer wizard + - with res_id 0, because they were created outside of an existing + wizard (typically user input through Chatter or reports + created on-the-fly by the templates) + - unused since at least one day (create_date and write_date) + """ + limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=1) + limit_date_str = datetime.datetime.strftime(limit_date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + self.env['ir.attachment'].search([ + ('res_model', '=', 'mail.compose.message'), + ('res_id', '=', 0), + ('create_date', '<', limit_date_str), + ('write_date', '<', limit_date_str)] + ).unlink() + return True + + @api.model + def check_mail_message_access(self, res_ids, operation, model_name=None): + """ mail.message check permission rules for related document. This method is + meant to be inherited in order to implement addons-specific behavior. + A common behavior would be to allow creating messages when having read + access rule on the document, for portal document such as issues. """ + if model_name: + DocModel = self.env[model_name] + else: + DocModel = self + if hasattr(DocModel, '_mail_post_access'): + create_allow = DocModel._mail_post_access + else: + create_allow = 'write' + + if operation in ['write', 'unlink']: + check_operation = 'write' + elif operation == 'create' and create_allow in ['create', 'read', 'write', 'unlink']: + check_operation = create_allow + elif operation == 'create': + check_operation = 'write' + else: + check_operation = operation + + DocModel.check_access_rights(check_operation) + DocModel.browse(res_ids).check_access_rule(check_operation) + + @api.multi + def message_change_thread(self, new_thread): + """ + Transfer the list of the mail thread messages from an model to another + + :param id : the old res_id of the mail.message + :param new_res_id : the new res_id of the mail.message + :param new_model : the name of the new model of the mail.message + + Example : my_lead.message_change_thread(my_project_task) + will transfer the context of the thread of my_lead to my_project_task + """ + self.ensure_one() + # get the subtype of the comment Message + subtype_comment = self.env.ref('mail.mt_comment') + + # get the ids of the comment and not-comment of the thread + # TDE check: sudo on mail.message, to be sure all messages are moved ? + MailMessage = self.env['mail.message'] + msg_comment = MailMessage.search([ + ('model', '=', self._name), + ('res_id', '=', self.id), + ('subtype_id', '=', subtype_comment.id)]) + msg_not_comment = MailMessage.search([ + ('model', '=', self._name), + ('res_id', '=', self.id), + ('subtype_id', '!=', subtype_comment.id)]) + + # update the messages + msg_comment.write({"res_id": new_thread.id, "model": new_thread._name}) + msg_not_comment.write({"res_id": new_thread.id, "model": new_thread._name, "subtype_id": None}) + return True + # ------------------------------------------------------ # Automatic log / Tracking # ------------------------------------------------------ @@ -504,55 +583,9 @@ class MailThread(models.AbstractModel): return True - #------------------------------------------------------ - # mail.message wrappers and tools - #------------------------------------------------------ - - @api.model - def _garbage_collect_attachments(self): - """ Garbage collect lost mail attachments. Those are attachments - - linked to res_model 'mail.compose.message', the composer wizard - - with res_id 0, because they were created outside of an existing - wizard (typically user input through Chatter or reports - created on-the-fly by the templates) - - unused since at least one day (create_date and write_date) - """ - limit_date = datetime.datetime.utcnow() - datetime.timedelta(days=1) - limit_date_str = datetime.datetime.strftime(limit_date, tools.DEFAULT_SERVER_DATETIME_FORMAT) - self.env['ir.attachment'].search([ - ('res_model', '=', 'mail.compose.message'), - ('res_id', '=', 0), - ('create_date', '<', limit_date_str), - ('write_date', '<', limit_date_str)] - ).unlink() - return True - - @api.model - def check_mail_message_access(self, res_ids, operation, model_name=None): - """ mail.message check permission rules for related document. This method is - meant to be inherited in order to implement addons-specific behavior. - A common behavior would be to allow creating messages when having read - access rule on the document, for portal document such as issues. """ - if model_name: - DocModel = self.env[model_name] - else: - DocModel = self - if hasattr(DocModel, '_mail_post_access'): - create_allow = DocModel._mail_post_access - else: - create_allow = 'write' - - if operation in ['write', 'unlink']: - check_operation = 'write' - elif operation == 'create' and create_allow in ['create', 'read', 'write', 'unlink']: - check_operation = create_allow - elif operation == 'create': - check_operation = 'write' - else: - check_operation = operation - - DocModel.check_access_rights(check_operation) - DocModel.browse(res_ids).check_access_rule(check_operation) + # ------------------------------------------------------ + # Email Notification + # ------------------------------------------------------ @api.model def _generate_notification_token(self, base_link, params): @@ -706,32 +739,6 @@ class MailThread(models.AbstractModel): return result - # ------------------------------------------------------ - # Email specific - # ------------------------------------------------------ - - @api.multi - def message_get_default_recipients(self, res_model=None, res_ids=None): - if res_model and res_ids: - if hasattr(self.env[res_model], 'message_get_default_recipients'): - return self.env[res_model].browse(res_ids).message_get_default_recipients() - records = self.env[res_model].sudo().browse(res_ids) - else: - records = self.sudo() - res = {} - for record in records: - recipient_ids, email_to, email_cc = set(), False, False - if 'partner_id' in self._fields and record.partner_id: - recipient_ids.add(record.partner_id.id) - elif 'email_from' in self._fields and record.email_from: - email_to = record.email_from - elif 'partner_email' in self._fields and record.partner_email: - email_to = record.partner_email - elif 'email' in self._fields: - email_to = record.email - res[record.id] = {'partner_ids': list(recipient_ids), 'email_to': email_to, 'email_cc': email_cc} - return res - @api.model def message_get_reply_to(self, res_ids, default=None): """ Returns the preferred reply-to email address that is basically the @@ -1550,9 +1557,31 @@ class MailThread(models.AbstractModel): msg_dict['body'], msg_dict['attachments'] = self._message_extract_payload(message, save_original=save_original) return msg_dict - #------------------------------------------------------ - # Note specific - #------------------------------------------------------ + # ------------------------------------------------------ + # Recipient management + # ------------------------------------------------------ + + @api.multi + def message_get_default_recipients(self, res_model=None, res_ids=None): + if res_model and res_ids: + if hasattr(self.env[res_model], 'message_get_default_recipients'): + return self.env[res_model].browse(res_ids).message_get_default_recipients() + records = self.env[res_model].sudo().browse(res_ids) + else: + records = self.sudo() + res = {} + for record in records: + recipient_ids, email_to, email_cc = set(), False, False + if 'partner_id' in self._fields and record.partner_id: + recipient_ids.add(record.partner_id.id) + elif 'email_from' in self._fields and record.email_from: + email_to = record.email_from + elif 'partner_email' in self._fields and record.partner_email: + email_to = record.partner_email + elif 'email' in self._fields: + email_to = record.email + res[record.id] = {'partner_ids': list(recipient_ids), 'email_to': email_to, 'email_cc': email_cc} + return res @api.multi def _message_add_suggested_recipient(self, result, partner=None, email=None, reason=''): @@ -1689,6 +1718,10 @@ class MailThread(models.AbstractModel): ]).write({'author_id': partner_info['partner_id']}) return result + # ------------------------------------------------------ + # Post / Send message API + # ------------------------------------------------------ + def _message_post_process_attachments(self, attachments, attachment_ids, message_data): """ Preprocess attachments for mail_thread.message_post() or mail_mail.create(). @@ -2148,40 +2181,3 @@ class MailThread(models.AbstractModel): self._message_auto_subscribe_notify(user_pids) return True - - # ------------------------------------------------------ - # Thread management - # ------------------------------------------------------ - - @api.multi - def message_change_thread(self, new_thread): - """ - Transfer the list of the mail thread messages from an model to another - - :param id : the old res_id of the mail.message - :param new_res_id : the new res_id of the mail.message - :param new_model : the name of the new model of the mail.message - - Example : my_lead.message_change_thread(my_project_task) - will transfer the context of the thread of my_lead to my_project_task - """ - self.ensure_one() - # get the subtype of the comment Message - subtype_comment = self.env.ref('mail.mt_comment') - - # get the ids of the comment and not-comment of the thread - # TDE check: sudo on mail.message, to be sure all messages are moved ? - MailMessage = self.env['mail.message'] - msg_comment = MailMessage.search([ - ('model', '=', self._name), - ('res_id', '=', self.id), - ('subtype_id', '=', subtype_comment.id)]) - msg_not_comment = MailMessage.search([ - ('model', '=', self._name), - ('res_id', '=', self.id), - ('subtype_id', '!=', subtype_comment.id)]) - - # update the messages - msg_comment.write({"res_id": new_thread.id, "model": new_thread._name}) - msg_not_comment.write({"res_id": new_thread.id, "model": new_thread._name, "subtype_id": None}) - return True diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index 77eca2873f1..ac3a56b3825 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -22,8 +22,10 @@ import sys import threading import time import types +import unicodedata import werkzeug.utils import zipfile + from collections import defaultdict, Iterable, Mapping, MutableSet, OrderedDict from itertools import islice, groupby, repeat from lxml import etree @@ -732,6 +734,19 @@ def attrgetter(*items): return tuple(resolve_attr(obj, attr) for attr in items) return g +# --------------------------------------------- +# String management +# --------------------------------------------- + +# Inspired by http://stackoverflow.com/questions/517923 +def remove_accents(input_str): + """Suboptimal-but-better-than-nothing way to replace accented + latin letters by an ASCII equivalent. Will obviously change the + meaning of input_str and work only for some cases""" + input_str = ustr(input_str) + nkfd_form = unicodedata.normalize('NFKD', input_str) + return u''.join([c for c in nkfd_form if not unicodedata.combining(c)]) + class unquote(str): """A subclass of str that implements repr() without enclosing quotation marks or escaping, keeping the original string untouched. The name come from Lisp's unquote.