You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
9.8 KiB

11 years ago
11 years ago
10 years ago
11 years ago
  1. # -*- coding: utf-8 -*-
  2. # TODO
  3. # pylint: disable=old-api7-method-defined,invalid-commit
  4. import base64
  5. import logging
  6. import re
  7. from email.utils import formataddr
  8. from openerp import SUPERUSER_ID, tools
  9. from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
  10. from openerp.osv import osv
  11. from openerp.tools.safe_eval import safe_eval
  12. from openerp.tools.translate import _
  13. _logger = logging.getLogger(__name__)
  14. class MailMail(osv.Model):
  15. _inherit = "mail.mail"
  16. def send(
  17. self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None
  18. ):
  19. # copy-paste from addons/mail/mail_mail.py
  20. """ Sends the selected emails immediately, ignoring their current
  21. state (mails that have already been sent should not be passed
  22. unless they should actually be re-sent).
  23. Emails successfully delivered are marked as 'sent', and those
  24. that fail to be deliver are marked as 'exception', and the
  25. corresponding error mail is output in the server logs.
  26. :param bool auto_commit: whether to force a commit of the mail status
  27. after sending each mail (meant only for scheduler processing);
  28. should never be True during normal transactions (default: False)
  29. :param bool raise_exception: whether to raise an exception if the
  30. email sending process has failed
  31. :return: True
  32. """
  33. # NEW STUFF
  34. catchall_alias = self.pool["ir.config_parameter"].get_param(
  35. cr, uid, "mail.catchall.alias_from", context=context
  36. )
  37. catchall_alias_name = self.pool["ir.config_parameter"].get_param(
  38. cr, uid, "mail.catchall.name_alias_from", context=context
  39. )
  40. catchall_domain = self.pool["ir.config_parameter"].get_param(
  41. cr, uid, "mail.catchall.domain", context=context
  42. )
  43. correct_email_from = r"@%s>?\s*$" % catchall_domain
  44. default_email_from = "{}@{}".format(catchall_alias, catchall_domain)
  45. context = dict(context or {})
  46. ir_mail_server = self.pool.get("ir.mail_server")
  47. ir_attachment = self.pool["ir.attachment"]
  48. for mail in self.browse(cr, SUPERUSER_ID, ids, context=context):
  49. try:
  50. # 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
  51. if mail.model:
  52. model_id = self.pool["ir.model"].search(
  53. cr, SUPERUSER_ID, [("model", "=", mail.model)], context=context
  54. )[0]
  55. model = self.pool["ir.model"].browse(
  56. cr, SUPERUSER_ID, model_id, context=context
  57. )
  58. else:
  59. model = None
  60. if model:
  61. context["model_name"] = model.name
  62. # load attachment binary data with a separate read(), as prefetching all
  63. # `datas` (binary field) could bloat the browse cache, triggerring
  64. # soft/hard mem limits with temporary data.
  65. attachment_ids = [a.id for a in mail.attachment_ids]
  66. attachments = [
  67. (a["datas_fname"], base64.b64decode(a["datas"]))
  68. for a in ir_attachment.read(
  69. cr, SUPERUSER_ID, attachment_ids, ["datas_fname", "datas"]
  70. )
  71. ]
  72. # specific behavior to customize the send email for notified partners
  73. email_list = []
  74. if mail.email_to:
  75. email_list.append(
  76. self.send_get_email_dict(cr, uid, mail, context=context)
  77. )
  78. for partner in mail.recipient_ids:
  79. email_list.append(
  80. self.send_get_email_dict(
  81. cr, uid, mail, partner=partner, context=context
  82. )
  83. )
  84. # headers
  85. headers = {}
  86. bounce_alias = self.pool["ir.config_parameter"].get_param(
  87. cr, uid, "mail.bounce.alias", context=context
  88. )
  89. catchall_domain = self.pool["ir.config_parameter"].get_param(
  90. cr, uid, "mail.catchall.domain", context=context
  91. )
  92. if bounce_alias and catchall_domain:
  93. if mail.model and mail.res_id:
  94. headers["Return-Path"] = "%s-%d-%s-%d@%s" % (
  95. bounce_alias,
  96. mail.id,
  97. mail.model,
  98. mail.res_id,
  99. catchall_domain,
  100. )
  101. else:
  102. headers["Return-Path"] = "%s-%d@%s" % (
  103. bounce_alias,
  104. mail.id,
  105. catchall_domain,
  106. )
  107. if mail.headers:
  108. try:
  109. headers.update(safe_eval(mail.headers))
  110. except Exception:
  111. pass
  112. # Writing on the mail object may fail (e.g. lock on user) which
  113. # would trigger a rollback *after* actually sending the email.
  114. # To avoid sending twice the same email, provoke the failure earlier
  115. mail.write({"state": "exception"})
  116. mail_sent = False
  117. # build an RFC2822 email.message.Message object and send it without queuing
  118. res = None
  119. for email in email_list:
  120. # NEW STUFF
  121. email_from = mail.email_from
  122. if re.search(correct_email_from, email_from) is None:
  123. email_from = default_email_from
  124. if catchall_alias_name:
  125. email_from = formataddr((catchall_alias_name, email_from))
  126. msg = ir_mail_server.build_email(
  127. email_from=email_from, # NEW STUFF
  128. email_to=email.get("email_to"),
  129. subject=email.get("subject"),
  130. body=email.get("body"),
  131. body_alternative=email.get("body_alternative"),
  132. email_cc=tools.email_split(mail.email_cc),
  133. reply_to=mail.reply_to,
  134. attachments=attachments,
  135. message_id=mail.message_id,
  136. references=mail.references,
  137. object_id=mail.res_id
  138. and ("{}-{}".format(mail.res_id, mail.model)),
  139. subtype="html",
  140. subtype_alternative="plain",
  141. headers=headers,
  142. )
  143. try:
  144. res = ir_mail_server.send_email(
  145. cr,
  146. uid,
  147. msg,
  148. mail_server_id=mail.mail_server_id.id,
  149. context=context,
  150. )
  151. except AssertionError as error:
  152. if str(error) == ir_mail_server.NO_VALID_RECIPIENT:
  153. # No valid recipient found for this particular
  154. # mail item -> ignore error to avoid blocking
  155. # delivery to next recipients, if any. If this is
  156. # the only recipient, the mail will show as failed.
  157. _logger.warning(
  158. "Ignoring invalid recipients for mail.mail %s: %s",
  159. mail.message_id,
  160. email.get("email_to"),
  161. )
  162. else:
  163. raise
  164. if res:
  165. mail.write({"state": "sent", "message_id": res})
  166. mail_sent = True
  167. # /!\ can't use mail.state here, as mail.refresh() will cause an error
  168. # see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
  169. if mail_sent:
  170. _logger.info(
  171. "Mail with ID %r and Message-Id %r successfully sent",
  172. mail.id,
  173. mail.message_id,
  174. )
  175. self._postprocess_sent_message(
  176. cr, uid, mail, context=context, mail_sent=mail_sent
  177. )
  178. except MemoryError:
  179. # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job
  180. # instead of marking the mail as failed
  181. _logger.exception(
  182. "MemoryError while processing mail with ID %r and Msg-Id %r. "
  183. "Consider raising the --limit-memory-hard startup option",
  184. mail.id,
  185. mail.message_id,
  186. )
  187. raise
  188. except Exception as e:
  189. _logger.exception("failed sending mail.mail %s", mail.id)
  190. mail.write({"state": "exception"})
  191. self._postprocess_sent_message(
  192. cr, uid, mail, context=context, mail_sent=False
  193. )
  194. if raise_exception:
  195. if isinstance(e, AssertionError):
  196. # get the args of the original error, wrap into a value and throw a MailDeliveryException
  197. # that is an except_orm, with name and value as arguments
  198. value = ". ".join(e.args)
  199. raise MailDeliveryException(_("Mail Delivery Failed"), value)
  200. raise
  201. if auto_commit is True:
  202. cr.commit()
  203. return True