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.

216 lines
9.7 KiB

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