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.

218 lines
9.7 KiB

11 years ago
10 years ago
11 years ago
  1. import base64
  2. import logging
  3. import re
  4. from email.utils import formataddr
  5. from odoo import SUPERUSER_ID, tools
  6. from odoo.osv import osv
  7. from odoo.tools.safe_eval import safe_eval as eval
  8. from odoo.tools.translate import _
  9. from odoo.addons.base.ir.ir_mail_server import MailDeliveryException
  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
  135. and ("{}-{}".format(mail.res_id, mail.model)),
  136. subtype="html",
  137. subtype_alternative="plain",
  138. headers=headers,
  139. )
  140. try:
  141. res = ir_mail_server.send_email(
  142. cr,
  143. uid,
  144. msg,
  145. mail_server_id=mail.mail_server_id.id,
  146. context=context,
  147. )
  148. except AssertionError as error:
  149. if str(error) == ir_mail_server.NO_VALID_RECIPIENT:
  150. # No valid recipient found for this particular
  151. # mail item -> ignore error to avoid blocking
  152. # delivery to next recipients, if any. If this is
  153. # the only recipient, the mail will show as failed.
  154. _logger.warning(
  155. "Ignoring invalid recipients for mail.mail %s: %s",
  156. mail.message_id,
  157. email.get("email_to"),
  158. )
  159. else:
  160. raise
  161. if res:
  162. mail.write({"state": "sent", "message_id": res})
  163. mail_sent = True
  164. # /!\ can't use mail.state here, as mail.refresh() will cause an error
  165. # see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
  166. if mail_sent:
  167. _logger.info(
  168. "Mail with ID %r and Message-Id %r successfully sent",
  169. mail.id,
  170. mail.message_id,
  171. )
  172. self._postprocess_sent_message(
  173. cr, uid, mail, context=context, mail_sent=mail_sent
  174. )
  175. except MemoryError:
  176. # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job
  177. # instead of marking the mail as failed
  178. _logger.exception(
  179. "MemoryError while processing mail with ID %r and Msg-Id %r. "
  180. "Consider raising the --limit-memory-hard startup option",
  181. mail.id,
  182. mail.message_id,
  183. )
  184. raise
  185. except Exception as e:
  186. _logger.exception("failed sending mail.mail %s", mail.id)
  187. mail.write({"state": "exception"})
  188. self._postprocess_sent_message(
  189. cr, uid, mail, context=context, mail_sent=False
  190. )
  191. if raise_exception:
  192. if isinstance(e, AssertionError):
  193. # get the args of the original error, wrap into a value and throw a MailDeliveryException
  194. # that is an except_orm, with name and value as arguments
  195. value = ". ".join(e.args)
  196. raise MailDeliveryException(_("Mail Delivery Failed"), value)
  197. raise
  198. if auto_commit is True:
  199. cr.commit()
  200. return True