浏览代码

[wip] add inline images in frappe.sendmail (#3528)

version-14
Rushabh Mehta 8 年前
committed by GitHub
父节点
当前提交
834ee797ca
共有 3 个文件被更改,包括 58 次插入26 次删除
  1. +5
    -2
      frappe/__init__.py
  2. +39
    -13
      frappe/email/email_body.py
  3. +14
    -11
      frappe/email/queue.py

+ 5
- 2
frappe/__init__.py 查看文件

@@ -379,7 +379,8 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, content=None, doctype=None, name=None, reply_to=None, attachments=None, content=None, doctype=None, name=None, reply_to=None,
cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None,
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False):
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False,
inline_images=None):
"""Send email using user's default **Email Account** or global default **Email Account**. """Send email using user's default **Email Account** or global default **Email Account**.




@@ -401,6 +402,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
:param send_after: Send after the given datetime. :param send_after: Send after the given datetime.
:param expose_recipients: Display all recipients in the footer message - "This email was sent to" :param expose_recipients: Display all recipients in the footer message - "This email was sent to"
:param communication: Communication link to be set in Email Queue record :param communication: Communication link to be set in Email Queue record
:param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id
""" """
message = content or message message = content or message


@@ -418,7 +420,8 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message,
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to, attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to,
send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority,
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification)
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification,
inline_images=inline_images)


whitelisted = [] whitelisted = []
guest_methods = [] guest_methods = []


+ 39
- 13
frappe/email/email_body.py 查看文件

@@ -2,19 +2,20 @@
# MIT License. See license.txt # MIT License. See license.txt


from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
import frappe, re
from frappe.utils.pdf import get_pdf from frappe.utils.pdf import get_pdf
from frappe.email.smtp import get_outgoing_email_account from frappe.email.smtp import get_outgoing_email_account
from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint, from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint,
split_emails, to_markdown, markdown, encode, random_string)
split_emails, to_markdown, markdown, encode, random_string, parse_addr)
import email.utils import email.utils
from frappe.utils import parse_addr
from six import iteritems from six import iteritems
from email.mime.multipart import MIMEMultipart




def get_email(recipients, sender='', msg='', subject='[No Subject]', def get_email(recipients, sender='', msg='', subject='[No Subject]',
text_content = None, footer=None, print_html=None, formatted=None, attachments=None, text_content = None, footer=None, print_html=None, formatted=None, attachments=None,
content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None):
content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None,
inline_images=[]):
"""send an html email as multipart with attachments and all""" """send an html email as multipart with attachments and all"""
content = content or msg content = content or msg
emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account, expose_recipients=expose_recipients) emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account, expose_recipients=expose_recipients)
@@ -22,7 +23,8 @@ def get_email(recipients, sender='', msg='', subject='[No Subject]',
if not content.strip().startswith("<"): if not content.strip().startswith("<"):
content = markdown(content) content = markdown(content)


emailobj.set_html(content, text_content, footer=footer, print_html=print_html, formatted=formatted)
emailobj.set_html(content, text_content, footer=footer,
print_html=print_html, formatted=formatted, inline_images=inline_images)


if isinstance(attachments, dict): if isinstance(attachments, dict):
attachments = [attachments] attachments = [attachments]
@@ -39,7 +41,6 @@ class EMail:
Also sets all messages as multipart/alternative for cleaner reading in text-only clients Also sets all messages as multipart/alternative for cleaner reading in text-only clients
""" """
def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None, expose_recipients=None): def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None, expose_recipients=None):
from email.mime.multipart import MIMEMultipart
from email import Charset from email import Charset
Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')


@@ -64,7 +65,8 @@ class EMail:


self.email_account = email_account or get_outgoing_email_account() self.email_account = email_account or get_outgoing_email_account()


def set_html(self, message, text_content = None, footer=None, print_html=None, formatted=None):
def set_html(self, message, text_content = None, footer=None, print_html=None,
formatted=None, inline_images=None):
"""Attach message in the html portion of multipart/alternative""" """Attach message in the html portion of multipart/alternative"""
if not formatted: if not formatted:
formatted = get_formatted_html(self.subject, message, footer, print_html, email_account=self.email_account) formatted = get_formatted_html(self.subject, message, footer, print_html, email_account=self.email_account)
@@ -77,7 +79,7 @@ class EMail:
else: else:
self.set_html_as_text(expand_relative_urls(formatted)) self.set_html_as_text(expand_relative_urls(formatted))


self.set_part_html(formatted)
self.set_part_html(formatted, inline_images)
self.html_set = True self.html_set = True


def set_text(self, message): def set_text(self, message):
@@ -88,10 +90,28 @@ class EMail:
part = MIMEText(message, 'plain', 'utf-8') part = MIMEText(message, 'plain', 'utf-8')
self.msg_multipart.attach(part) self.msg_multipart.attach(part)


def set_part_html(self, message):
def set_part_html(self, message, inline_images):
from email.mime.text import MIMEText from email.mime.text import MIMEText
part = MIMEText(message, 'html', 'utf-8')
self.msg_multipart.attach(part)
if inline_images:
related = MIMEMultipart('related')

for image in inline_images:
# images in dict like {filename:'', filecontent:'raw'}
content_id = random_string(10)

# replace filename in message with CID
message = re.sub('''src=['"]{0}['"]'''.format(image.get('filename')),
'src="cid:{0}"'.format(content_id), message)

self.add_attachment(image.get('filename'), image.get('filecontent'),
None, content_id=content_id, parent=related)

html_part = MIMEText(message, 'html', 'utf-8')
related.attach(html_part)

self.msg_multipart.attach(related)
else:
self.msg_multipart.attach(MIMEText(message, 'html', 'utf-8'))


def set_html_as_text(self, html): def set_html_as_text(self, html):
"""return html2text""" """return html2text"""
@@ -118,7 +138,8 @@ class EMail:


self.add_attachment(res[0], res[1]) self.add_attachment(res[0], res[1])


def add_attachment(self, fname, fcontent, content_type=None):
def add_attachment(self, fname, fcontent, content_type=None,
parent=None, content_id=None):
"""add attachment""" """add attachment"""
from email.mime.audio import MIMEAudio from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase from email.mime.base import MIMEBase
@@ -155,8 +176,13 @@ class EMail:
if fname: if fname:
part.add_header(b'Content-Disposition', part.add_header(b'Content-Disposition',
("attachment; filename=\"%s\"" % fname).encode('utf-8')) ("attachment; filename=\"%s\"" % fname).encode('utf-8'))
if content_id:
part.add_header(b'Content-ID', '<{0}>'.format(content_id))


self.msg_root.attach(part)
if not parent:
parent = self.msg_root

parent.attach(part)


def add_pdf_attachment(self, name, html, options=None): def add_pdf_attachment(self, name, html, options=None):
self.add_attachment(name, get_pdf(html, options), 'application/octet-stream') self.add_attachment(name, get_pdf(html, options), 'application/octet-stream')


+ 14
- 11
frappe/email/queue.py 查看文件

@@ -21,7 +21,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None, attachments=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None,
expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None, expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None,
queue_separately=False, is_notification=False, add_unsubscribe_link=1):
queue_separately=False, is_notification=False, add_unsubscribe_link=1, inline_images=None):
"""Add email to sending queue (Email Queue) """Add email to sending queue (Email Queue)


:param recipients: List of recipients. :param recipients: List of recipients.
@@ -42,6 +42,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
:param queue_separately: Queue each email separately :param queue_separately: Queue each email separately
:param is_notification: Marks email as notification so will not trigger notifications from system :param is_notification: Marks email as notification so will not trigger notifications from system
:param add_unsubscribe_link: Send unsubscribe link in the footer of the Email, default 1. :param add_unsubscribe_link: Send unsubscribe link in the footer of the Email, default 1.
:param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id
""" """
if not unsubscribe_method: if not unsubscribe_method:
unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe" unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe"
@@ -112,6 +113,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
read_receipt=read_receipt, read_receipt=read_receipt,
queue_separately=queue_separately, queue_separately=queue_separately,
is_notification = is_notification, is_notification = is_notification,
inline_images = inline_images,
now=now) now=now)




@@ -152,7 +154,8 @@ def get_email_queue(recipients, sender, subject, **kwargs):
reply_to=kwargs.get('reply_to'), reply_to=kwargs.get('reply_to'),
cc=kwargs.get('cc'), cc=kwargs.get('cc'),
email_account=kwargs.get('email_account'), email_account=kwargs.get('email_account'),
expose_recipients=kwargs.get('expose_recipients'))
expose_recipients=kwargs.get('expose_recipients'),
inline_images=kwargs.get('inline_images'))


mail.set_message_id(kwargs.get('message_id'),kwargs.get('is_notification')) mail.set_message_id(kwargs.get('message_id'),kwargs.get('is_notification'))
if kwargs.get('read_receipt'): if kwargs.get('read_receipt'):
@@ -431,7 +434,7 @@ def prepare_message(email, recipient, recipients_list):
message = email.message message = email.message
if not message: if not message:
return "" return ""
if email.add_unsubscribe_link and email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url if email.add_unsubscribe_link and email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url
unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient, unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient,
email.unsubscribe_method, email.unsubscribe_params) email.unsubscribe_method, email.unsubscribe_params)
@@ -461,19 +464,19 @@ def clear_outbox():
Called daily via scheduler. Called daily via scheduler.
Note: Used separate query to avoid deadlock Note: Used separate query to avoid deadlock
""" """
email_queues = frappe.db.sql_list("""select name from `tabEmail Queue`
email_queues = frappe.db.sql_list("""select name from `tabEmail Queue`
where priority=0 and datediff(now(), modified) > 31""") where priority=0 and datediff(now(), modified) > 31""")
if email_queues: if email_queues:
frappe.db.sql("""delete from `tabEmail Queue` where name in (%s)"""
frappe.db.sql("""delete from `tabEmail Queue` where name in (%s)"""
% ','.join(['%s']*len(email_queues)), tuple(email_queues)) % ','.join(['%s']*len(email_queues)), tuple(email_queues))
frappe.db.sql("""delete from `tabEmail Queue Recipient` where parent in (%s)"""
frappe.db.sql("""delete from `tabEmail Queue Recipient` where parent in (%s)"""
% ','.join(['%s']*len(email_queues)), tuple(email_queues)) % ','.join(['%s']*len(email_queues)), tuple(email_queues))
for dt in ("Email Queue", "Email Queue Recipient"): for dt in ("Email Queue", "Email Queue Recipient"):
frappe.db.sql(""" frappe.db.sql("""
update `tab{0}`
update `tab{0}`
set status='Expired' set status='Expired'
where datediff(curdate(), modified) > 7 and status='Not Sent'""".format(dt)) where datediff(curdate(), modified) > 7 and status='Not Sent'""".format(dt))

正在加载...
取消
保存