From ce9d37e8e3bb6053e8740d11a83c3e630c792652 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Fri, 20 Mar 2015 20:15:51 +0200 Subject: [PATCH] [REF] come back to origin idea of redefining "send" function, because nothing else works * rewriting email_from field before executing "send" doesn't work because we lost email_from value of incoming email if sender was not in res.partner * it's impossible to inhering build_email function, because we don't have CR variable there. --- README.rst | 14 +++++ mail_fix_553.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 163 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 1cb7e68..7adff21 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,20 @@ E.g: if you mail service provider, e.g. pdd.yandex.ru, doesn't allow emails with a FROM value differ from ...@MYDOMAIN.COM, then you get 553. This is why you need to update FROM value to portal@MYDOMAIN.COM +Configuration +============= + You can configure default alias at Settings -> System Parameters -> mail.catchall.alias_from +Known issues / Roadmap +====================== + +The module is consist of redefined send function from mail.mail +model. So it is just copy pasted source code with some +modification. This function is changed very rarely, but sometime it +can happens and the module should be updated. You can check commits +for mail_mail.py here: +https://github.com/odoo/odoo/commits/8.0/addons/mail/mail_mail.py + + Tested on Odoo 8.0 d023c079ed86468436f25da613bf486a4a17d625 diff --git a/mail_fix_553.py b/mail_fix_553.py index 3df265d..3aae65b 100644 --- a/mail_fix_553.py +++ b/mail_fix_553.py @@ -1,15 +1,40 @@ # -*- coding: utf-8 -*- -import re + +import base64 +import logging +from email.utils import formataddr +from urlparse import urljoin + +from openerp import api, tools from openerp import SUPERUSER_ID +from openerp.addons.base.ir.ir_mail_server import MailDeliveryException from openerp.osv import fields, osv +from openerp.tools.safe_eval import safe_eval as eval +from openerp.tools.translate import _ -import logging _logger = logging.getLogger(__name__) -class mail_mail(osv.osv): +class mail_mail(osv.Model): _inherit = "mail.mail" - def send(self, cr, uid, ids, context=None, **kwargs): + def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): + # copy-paste from addons/mail/mail_mail.py + """ Sends the selected emails immediately, ignoring their current + state (mails that have already been sent should not be passed + unless they should actually be re-sent). + Emails successfully delivered are marked as 'sent', and those + that fail to be deliver are marked as 'exception', and the + corresponding error mail is output in the server logs. + + :param bool auto_commit: whether to force a commit of the mail status + after sending each mail (meant only for scheduler processing); + should never be True during normal transactions (default: False) + :param bool raise_exception: whether to raise an exception if the + email sending process has failed + :return: True + """ + + # NEW STUFF catchall_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.alias_from", context=context) catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context) @@ -17,6 +42,126 @@ class mail_mail(osv.osv): default_email_from = '%s@%s' % (catchall_alias, catchall_domain) + context = dict(context or {}) + ir_mail_server = self.pool.get('ir.mail_server') + ir_attachment = self.pool['ir.attachment'] + for mail in self.browse(cr, SUPERUSER_ID, ids, context=context): + try: + # TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method + if mail.model: + model_id = self.pool['ir.model'].search(cr, SUPERUSER_ID, [('model', '=', mail.model)], context=context)[0] + model = self.pool['ir.model'].browse(cr, SUPERUSER_ID, model_id, context=context) + else: + model = None + if model: + context['model_name'] = model.name + + # load attachment binary data with a separate read(), as prefetching all + # `datas` (binary field) could bloat the browse cache, triggerring + # soft/hard mem limits with temporary data. + attachment_ids = [a.id for a in mail.attachment_ids] + attachments = [(a['datas_fname'], base64.b64decode(a['datas'])) + for a in ir_attachment.read(cr, SUPERUSER_ID, attachment_ids, + ['datas_fname', 'datas'])] + + # specific behavior to customize the send email for notified partners + email_list = [] + if mail.email_to: + email_list.append(self.send_get_email_dict(cr, uid, mail, context=context)) + for partner in mail.recipient_ids: + email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context)) + # headers + headers = {} + bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context) + catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context) + if bounce_alias and catchall_domain: + if mail.model and mail.res_id: + headers['Return-Path'] = '%s-%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain) + else: + headers['Return-Path'] = '%s-%d@%s' % (bounce_alias, mail.id, catchall_domain) + if mail.headers: + try: + headers.update(eval(mail.headers)) + except Exception: + pass + + # Writing on the mail object may fail (e.g. lock on user) which + # would trigger a rollback *after* actually sending the email. + # To avoid sending twice the same email, provoke the failure earlier + mail.write({'state': 'exception'}) + mail_sent = False + + # build an RFC2822 email.message.Message object and send it without queuing + res = None + for email in email_list: + + # NEW STUFF + email_from = mail.email_from + if re.search(correct_email_from, email_from) is None: + email_from = default_email_from + + msg = ir_mail_server.build_email( + email_from=email_from, # NEW STUFF + email_to=email.get('email_to'), + subject=email.get('subject'), + body=email.get('body'), + body_alternative=email.get('body_alternative'), + email_cc=tools.email_split(mail.email_cc), + reply_to=mail.reply_to, + attachments=attachments, + message_id=mail.message_id, + references=mail.references, + object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), + subtype='html', + subtype_alternative='plain', + headers=headers) + try: + res = ir_mail_server.send_email(cr, uid, msg, + mail_server_id=mail.mail_server_id.id, + context=context) + except AssertionError as error: + if error.message == ir_mail_server.NO_VALID_RECIPIENT: + # No valid recipient found for this particular + # mail item -> ignore error to avoid blocking + # delivery to next recipients, if any. If this is + # the only recipient, the mail will show as failed. + _logger.warning("Ignoring invalid recipients for mail.mail %s: %s", + mail.message_id, email.get('email_to')) + else: + raise + if res: + mail.write({'state': 'sent', 'message_id': res}) + mail_sent = True + + # /!\ can't use mail.state here, as mail.refresh() will cause an error + # see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1 + if mail_sent: + _logger.info('Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) + self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent) + except MemoryError: + # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job + # instead of marking the mail as failed + _logger.exception('MemoryError while processing mail with ID %r and Msg-Id %r. '\ + 'Consider raising the --limit-memory-hard startup option', + mail.id, mail.message_id) + raise + except Exception as e: + _logger.exception('failed sending mail.mail %s', mail.id) + mail.write({'state': 'exception'}) + self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=False) + if raise_exception: + if isinstance(e, AssertionError): + # get the args of the original error, wrap into a value and throw a MailDeliveryException + # that is an except_orm, with name and value as arguments + value = '. '.join(e.args) + raise MailDeliveryException(_("Mail Delivery Failed"), value) + raise + + if auto_commit is True: + cr.commit() + return True + + for mail in self.browse(cr, SUPERUSER_ID, ids, context=context): email_from = mail.email_from if not email_from or re.search(correct_email_from, email_from) is None: