diff options
| author | Thibault Delavallée <tde@odoo.com> | 2018-02-14 13:07:25 +0100 |
|---|---|---|
| committer | Thibault Delavallée <tde@odoo.com> | 2018-05-07 10:58:54 +0200 |
| commit | 282733cf8df7bfe7cae17fd726d1caf759690169 (patch) | |
| tree | 4e0802c19a7114af7ba5944861b0812cf2ad2501 /addons/mail | |
| parent | 6cb5052e856a83914611898cd86d810e3ef15148 (diff) | |
[REF] mail: make notification methods semi-generic
mail.thread mixing class defines several methods used in notification process
that are not specific to mail.thread enabled models. It means some models
could override it while some other models do not even have a direct access
to that method.
When calling those methods we have to manually check for method being
available on a record or call a default implementation from mail.thread
even if the model does not inherit from the mixin.
In this commit we propose to hide this check in generic wrappers located
in mail.thread class. It checks for method existence on record and call
the model-specific implementation or falls back on standard behavior. It
allows to be sure the right method is called.
Functionally behavior should stay globally the same. However when updating
some addons code has been updated. Previous code could lead to records not
having correct alias which should not be the case anymore. This commit is
linked to task ID 47934.
Diffstat (limited to 'addons/mail')
| -rw-r--r-- | addons/mail/models/mail_channel.py | 1 | ||||
| -rw-r--r-- | addons/mail/models/mail_message.py | 11 | ||||
| -rw-r--r-- | addons/mail/models/mail_thread.py | 171 | ||||
| -rw-r--r-- | addons/mail/models/res_partner.py | 17 | ||||
| -rw-r--r-- | addons/mail/wizard/mail_compose_message.py | 7 |
5 files changed, 141 insertions, 66 deletions
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index f2a2f176a6b..d5b70f08014 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -198,7 +198,6 @@ class Channel(models.Model): @api.multi def _notify_specific_email_values(self, message): - self.ensure_one() res = super(Channel, self)._notify_specific_email_values(message) try: headers = safe_eval(res.get('headers', dict())) diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 4362358b864..45d66dac6a5 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -695,15 +695,10 @@ class Message(models.Model): @api.model def _get_reply_to(self, values): - """ Return a specific reply_to: alias of the document through - _notify_get_reply_to or take the email_from """ + """ Return a specific reply_to for the document """ model, res_id, email_from = values.get('model', self._context.get('default_model')), values.get('res_id', self._context.get('default_res_id')), values.get('email_from') # ctx values / defualt_get res ? - if model and hasattr(self.env[model], '_notify_get_reply_to'): - # return self.env[model].browse(res_id)._notify_get_reply_to([res_id], default=email_from)[res_id] - return self.env[model]._notify_get_reply_to([res_id], default=email_from)[res_id] - else: - # return self.env['mail.thread']._notify_get_reply_to(default=email_from)[None] - return self.env['mail.thread']._notify_get_reply_to([None], default=email_from)[None] + records = self.env[model].browse([res_id]) if model and res_id else None + return self.env['mail.thread']._notify_get_reply_to_on_records(default=email_from, records=records)[res_id or False] @api.model def _get_message_id(self, values): diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index f92b8006fc3..2ce459cce4e 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -665,14 +665,22 @@ class MailThread(models.AbstractModel): Each action is a dict containing url and title of the button. Groups has a default value that you can find in mail_thread - _notify_classify_recipients method. + ``_notify_classify_recipients`` method. """ return groups @api.multi def _notify_classify_recipients(self, message, recipients): - # At this point, all access rights should be ok. We sudo everything to - # access rights checks and speedup the computation. + """ Classify recipients to be notified of a message in groups to have + specific rendering depending on their group. For example users could + have access to buttons customers should not have in their emails. + + Module-specific grouping should be done by overriding ``_notify_get_groups`` + method defined here-under. + + :param message: mail.message record about to be notified + :param recipients: res.partner recordset to notify + """ result = {} access_link = self._notify_get_action_link('view') @@ -714,56 +722,115 @@ class MailThread(models.AbstractModel): return result - @api.model - def _notify_get_reply_to(self, res_ids, default=None): - """ Returns the preferred reply-to email address that is basically the - alias of the document, if it exists. Override this method to implement - a custom behavior about reply-to for generated emails. """ - model_name = self.env.context.get('thread_model') or self._name + def _notify_classify_recipients_on_records(self, message, recipients, records=None): + """ Generic wrapper on ``_notify_classify_recipients`` checking mail.thread + inheritance and allowing to call model-specific implementation in a one liner. + This method should not be overridden. """ + if records and hasattr(records, '_notify_classify_recipients'): + return records._notify_classify_recipients(message, recipients) + return self._notify_classify_recipients(message, recipients) + + @api.multi + def _notify_get_reply_to(self, default=None, records=None, company=None, doc_names=None): + """ Returns the preferred reply-to email address when replying to a thread + on documents. Documents are either given by self it this method is called + as a true multi method on a record set or can be given using records to + have a generic implementation available for all models. + + Reply-to is formatted like "MyCompany MyDocument <reply.to@domain>". + Heuristic it the following: + + * search for specific aliases as they always have priority; it is limited + to aliases linked to documents (like project alias for task for example); + * use catchall address; + * use default; + + This method works on documents + + * as a true multi method for models inheriting from mail.thread; call + ``records._notify_get_reply_to(...)``; + * as a generic implementation if records are given; call ``MailThread. + _notify_get_reply_to(records=records, ...)``; + * as a generic implementation is self is a void mail.thread record set + meaning catchall is computed; call ``MailThread._notify_get_reply_to + (records=None)``; + + Tweak this method on a specific model by overriding if it inherits from + mail.thread. An example would be tasks taking their reply-to alias from + their project. + + :param default: default email if no alias or catchall is found; + :param records: record_set if self if a generic mail.thread record allowing + generic implementation of finding reply-to; + :param company: used to compute company name part of the from name; provide + it if already known, otherwise fall back on user company; + :param doc_names: dict(res_id, doc_name) used to compute doc name part of + the from name; provide it if already known to avoid queries, otherwise + name_get on document will be performed; + + :return result: dictionary. Keys are record IDs and value is formatted + like an email "Company_name Document_name <reply_to@email>"/ + """ + _records = self if self and self._name != 'mail.thread' else records + model = _records._name if _records and _records._name != 'mail.thread' else False + res_ids = _records.ids if _records and model else [] + _res_ids = res_ids or [False] # always have a default value located in False + alias_domain = self.env['ir.config_parameter'].sudo().get_param("mail.catchall.domain") - res = dict.fromkeys(res_ids, False) + result = dict.fromkeys(_res_ids, False) + result_email = dict() + doc_names = doc_names if doc_names else dict() - # alias domain: check for aliases and catchall - aliases = {} - doc_names = {} if alias_domain: - if model_name and model_name != 'mail.thread' and res_ids: + if model and res_ids: + if not doc_names: + doc_names = dict((rec.id, rec.display_name) for rec in _records) + mail_aliases = self.env['mail.alias'].sudo().search([ - ('alias_parent_model_id.model', '=', model_name), + ('alias_parent_model_id.model', '=', model), ('alias_parent_thread_id', 'in', res_ids), ('alias_name', '!=', False)]) - # take only first found alias for each thread_id, to match - # order (1 found -> limit=1 for each res_id) + # take only first found alias for each thread_id, to match order (1 found -> limit=1 for each res_id) for alias in mail_aliases: - if alias.alias_parent_thread_id not in aliases: - aliases[alias.alias_parent_thread_id] = '%s@%s' % (alias.alias_name, alias_domain) - doc_names.update( - dict((ng_res[0], ng_res[1]) - for ng_res in self.env[model_name].sudo().browse(aliases).name_get())) + result_email.setdefault(alias.alias_parent_thread_id, '%s@%s' % (alias.alias_name, alias_domain)) + # left ids: use catchall - left_ids = set(res_ids).difference(set(aliases)) + left_ids = set(_res_ids) - set(result_email) if left_ids: - catchall_alias = self.env['ir.config_parameter'].sudo().get_param("mail.catchall.alias") - if catchall_alias: - aliases.update(dict((res_id, '%s@%s' % (catchall_alias, alias_domain)) for res_id in left_ids)) - # compute name of reply-to - company_name = self.env.user.company_id.name - for res_id in aliases: - email_name = '%s%s' % (company_name, doc_names.get(res_id) and (' ' + doc_names[res_id]) or '') - email_addr = aliases[res_id] - res[res_id] = formataddr((email_name, email_addr)) - left_ids = set(res_ids).difference(set(aliases)) + catchall = self.env['ir.config_parameter'].sudo().get_param("mail.catchall.alias") + if catchall: + result_email.update(dict((rid, '%s@%s' % (catchall, alias_domain)) for rid in left_ids)) + + # compute name of reply-to - TDE tocheck: quotes and stuff like that + company_name = company.name if company else self.env.user.company_id.name + for res_id in result_email.keys(): + name = '%s%s%s' % (company_name, ' ' if doc_names.get(res_id) else '', doc_names.get(res_id, '')) + result[res_id] = formataddr((name, result_email[res_id])) + + left_ids = set(_res_ids) - set(result_email) if left_ids: - res.update(dict((res_id, default) for res_id in res_ids)) - return res + result.update(dict((res_id, default) for res_id in left_ids)) + + return result + + @api.model + def _notify_get_reply_to_on_records(self, default=None, records=None, company=None, doc_names=None): + """ Generic wrapper on ``_notify_get_reply_to`` checking mail.thread inheritance + and allowing to call model-specific implementation in a one liner. This + method should not be overridden. """ + if records and hasattr(records, '_notify_get_reply_to'): + return records._notify_get_reply_to(default=default, company=company, doc_names=doc_names) + return self._notify_get_reply_to(default=default, records=records, company=company, doc_names=doc_names) @api.multi def _notify_specific_email_values(self, message): """ Get specific notification email values to store on the notification mail.mail. Override to add values related to a specific model. - :param MailMessage message: mail.message record being notified by email + :param message: mail.message record being notified by email """ + if not self: + return {} self.ensure_one() database_uuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid') return {'headers': repr({ @@ -771,6 +838,15 @@ class MailThread(models.AbstractModel): 'X-Odoo-db-uuid': database_uuid })} + @api.model + def _notify_specific_email_values_on_records(self, message, records=None): + """ Generic wrapper on ``_notify_specific_email_values`` checking mail.thread + inheritance and allowing to call model-specific implementation in a one liner. + This method should not be overridden. """ + if records and hasattr(records, '_notify_specific_email_values'): + return records._notify_specific_email_values(message) + return self._notify_specific_email_values(message) + @api.multi def _notify_email_recipients(self, message, recipient_ids): """ Format email notification recipient values to store on the notification @@ -778,13 +854,22 @@ class MailThread(models.AbstractModel): recipients. Override to generate other mail values like email_to or email_cc. - :param MailMessage message: mail.message record being notified by email - :param list recipient_ids: list of res.partner ids to notify + :param message: mail.message record being notified by email + :param recipient_ids: res.partner recordset to notify """ return { 'recipient_ids': [(4, pid) for pid in recipient_ids] } + @api.model + def _notify_email_recipients_on_records(self, message, recipient_ids, records=None): + """ Generic wrapper on ``_notify_email_recipients`` checking mail.thread + inheritance and allowing to call model-specific implementation in a one liner. + This method should not be overridden. """ + if records and hasattr(records, '_notify_email_recipients'): + return records._notify_email_recipients(message, recipient_ids) + return self._notify_email_recipients(message, recipient_ids) + # ------------------------------------------------------ # Mail gateway # ------------------------------------------------------ @@ -1977,18 +2062,19 @@ class MailThread(models.AbstractModel): author = self.env.user.partner_id if not author.email: raise exceptions.UserError(_("Unable to notify message, please configure the sender's email address.")) + email_from = formataddr((author.name, author.email)) msg_values = { 'subject': subject, 'body': body, 'author_id': author.id, - 'email_from': formataddr((author.name, author.email)), + 'email_from': email_from, 'message_type': 'notification', 'partner_ids': partner_ids, 'model': False, 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'), 'record_name': False, - 'reply_to': self.env['mail.thread'].sudo()._notify_get_reply_to([0])[0], + 'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from, records=None)[False], 'message_id': tools.generate_tracking_message_id('message-notify'), } msg_values.update(kwargs) @@ -2011,18 +2097,19 @@ class MailThread(models.AbstractModel): author = self.env.user.partner_id if not author.email: raise exceptions.UserError(_("Unable to log message, please configure the sender's email address.")) + email_from = formataddr((author.name, author.email)) message_values = { 'subject': subject, 'body': body, 'author_id': author.id, - 'email_from': formataddr((author.name, author.email)), + 'email_from': email_from, 'message_type': message_type, 'model': kwargs.get('model', self._name), 'res_id': self.ids[0] if self.ids else False, 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'), 'record_name': False, - 'reply_to': self.env['mail.thread'].sudo()._notify_get_reply_to([0])[0], + 'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from, records=None)[False], 'message_id': tools.generate_tracking_message_id('message-notify'), } message_values.update(kwargs) diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py index 09554230025..f6472eafd0b 100644 --- a/addons/mail/models/res_partner.py +++ b/addons/mail/models/res_partner.py @@ -100,8 +100,8 @@ class Partner(models.Model): # custom values custom_values = dict() - if message.res_id and message.model in self.env and hasattr(self.env[message.model], '_notify_specific_email_values'): - custom_values = self.env[message.model].browse(message.res_id)._notify_specific_email_values(message) + if message.res_id and message.model: + custom_values = self.env['mail.thread']._notify_specific_email_values_on_records(message, records=self.env[message.model].browse(message.res_id)) mail_values = { 'mail_message_id': message.id, @@ -122,11 +122,8 @@ class Partner(models.Model): # cache so should not impact performances. mail_message_id = mail_values.get('mail_message_id') message = self.env['mail.message'].browse(mail_message_id) if mail_message_id else None - if message and message.model and message.res_id and message.model in self.env and hasattr(self.env[message.model], '_notify_email_recipients'): - tig = self.env[message.model].browse(message.res_id) - recipient_values = tig._notify_email_recipients(message, email_chunk) - else: - recipient_values = self.env['mail.thread']._notify_email_recipients(message, email_chunk) + tig = self.env[message.model].browse(message.res_id) if message and message.model and message.res_id else False + recipient_values = self.env['mail.thread']._notify_email_recipients_on_records(message, email_chunk, records=tig) create_values = { 'body_html': body, 'subject': subject, @@ -178,10 +175,8 @@ class Partner(models.Model): base_mail_values = self._notify_prepare_email_values(message, values) # classify recipients: actions / no action - if message.model and message.res_id and hasattr(self.env[message.model], '_notify_classify_recipients'): - recipients = self.env[message.model].browse(message.res_id)._notify_classify_recipients(message, self) - else: - recipients = self.env['mail.thread']._notify_classify_recipients(message, self) + tig = self.env[message.model].browse(message.res_id) if message.model and message.res_id else False + recipients = self.env['mail.thread']._notify_classify_recipients_on_records(message, self, records=tig) emails = self.env['mail.mail'] recipients_nbr, recipients_max = 0, 50 diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 4cc06574ec4..91718f21f8a 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -278,8 +278,8 @@ class MailComposer(models.TransientModel): # compute alias-based reply-to in batch reply_to_value = dict.fromkeys(res_ids, None) if mass_mail_mode and not self.no_auto_thread: - # reply_to_value = self.env['mail.thread'].with_context(thread_model=self.model).browse(res_ids)._notify_get_reply_to(default=self.email_from) - reply_to_value = self.env['mail.thread'].with_context(thread_model=self.model)._notify_get_reply_to(res_ids, default=self.email_from) + records = self.env[self.model].browse(res_ids) + reply_to_value = self.env['mail.thread']._notify_get_reply_to_on_records(default=self.email_from, records=records) for res_id in res_ids: # static wizard (mail.message) values @@ -299,8 +299,7 @@ class MailComposer(models.TransientModel): # mass mailing: rendering override wizard static values if mass_mail_mode and self.model: - if self.model in self.env and hasattr(self.env[self.model], '_notify_specific_email_values'): - mail_values.update(self.env[self.model].browse(res_id)._notify_specific_email_values(False)) + mail_values.update(self.env['mail.thread']._notify_specific_email_values_on_records(False, records=self.env[self.model].browse(res_id))) # keep a copy unless specifically requested, reset record name (avoid browsing records) mail_values.update(notification=not self.auto_delete_message, model=self.model, res_id=res_id, record_name=False) # auto deletion of mail_mail |
