浏览代码

Merge pull request #13393 from leela/email-queue

refactor: build Email queue from send mail request
version-14
Leela vadlamudi 4 年前
committed by GitHub
父节点
当前提交
b40a82a659
找不到此签名对应的密钥 GPG 密钥 ID: 4AEE18F83AFDEB23
共有 8 个文件被更改,包括 332 次插入283 次删除
  1. +7
    -3
      frappe/__init__.py
  2. +301
    -6
      frappe/email/doctype/email_queue/email_queue.py
  3. +0
    -1
      frappe/email/doctype/newsletter/newsletter.py
  4. +2
    -250
      frappe/email/queue.py
  5. +5
    -4
      frappe/email/smtp.py
  6. +9
    -12
      frappe/email/test_email_body.py
  7. +7
    -6
      frappe/tests/test_email.py
  8. +1
    -1
      frappe/utils/error.py

+ 7
- 3
frappe/__init__.py 查看文件

@@ -528,16 +528,20 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
if not delayed: if not delayed:
now = True now = True


from frappe.email import queue
queue.send(recipients=recipients, sender=sender,
from frappe.email.doctype.email_queue.email_queue import QueueBuilder
builder = QueueBuilder(recipients=recipients, sender=sender,
subject=subject, message=message, text_content=text_content, subject=subject, message=message, text_content=text_content,
reference_doctype = doctype or reference_doctype, reference_name = name or reference_name, add_unsubscribe_link=add_unsubscribe_link, reference_doctype = doctype or reference_doctype, reference_name = name or reference_name, add_unsubscribe_link=add_unsubscribe_link,
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, bcc=bcc, message_id=message_id, in_reply_to=in_reply_to, attachments=attachments, reply_to=reply_to, cc=cc, bcc=bcc, message_id=message_id, in_reply_to=in_reply_to,
send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, queue_separately=queue_separately, send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, queue_separately=queue_separately,
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification,
communication=communication, read_receipt=read_receipt, is_notification=is_notification,
inline_images=inline_images, header=header, print_letterhead=print_letterhead, with_container=with_container) inline_images=inline_images, header=header, print_letterhead=print_letterhead, with_container=with_container)


# build email queue and send the email if send_now is True.
builder.process(send_now=now)


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


+ 301
- 6
frappe/email/doctype/email_queue/email_queue.py 查看文件

@@ -9,14 +9,18 @@ from rq.timeouts import JobTimeoutException
import smtplib import smtplib
import quopri import quopri
from email.parser import Parser from email.parser import Parser
from email.policy import SMTPUTF8
from html2text import html2text
from six.moves import html_parser as HTMLParser


import frappe import frappe
from frappe import _, safe_encode, task from frappe import _, safe_encode, task
from frappe.model.document import Document from frappe.model.document import Document
from frappe.email.queue import get_unsubcribed_url
from frappe.email.email_body import add_attachment
from frappe.utils import cint
from email.policy import SMTPUTF8
from frappe.email.queue import get_unsubcribed_url, get_unsubscribe_message
from frappe.email.email_body import add_attachment, get_formatted_html, get_email
from frappe.utils import cint, split_emails, add_days, nowdate, cstr
from frappe.email.doctype.email_account.email_account import EmailAccount



MAX_RETRY_COUNT = 3 MAX_RETRY_COUNT = 3
class EmailQueue(Document): class EmailQueue(Document):
@@ -41,6 +45,19 @@ class EmailQueue(Document):
duplicate.set_recipients(recipients) duplicate.set_recipients(recipients)
return duplicate return duplicate


@classmethod
def new(cls, doc_data, ignore_permissions=False):
data = doc_data.copy()
if not data.get('recipients'):
return

recipients = data.pop('recipients')
doc = frappe.new_doc(cls.DOCTYPE)
doc.update(data)
doc.set_recipients(recipients)
doc.insert(ignore_permissions=ignore_permissions)
return doc

@classmethod @classmethod
def find(cls, name): def find(cls, name):
return frappe.get_doc(cls.DOCTYPE, name) return frappe.get_doc(cls.DOCTYPE, name)
@@ -74,8 +91,6 @@ class EmailQueue(Document):
return json.loads(self.attachments) if self.attachments else [] return json.loads(self.attachments) if self.attachments else []


def get_email_account(self): def get_email_account(self):
from frappe.email.doctype.email_account.email_account import EmailAccount

if self.email_account: if self.email_account:
return frappe.get_doc('Email Account', self.email_account) return frappe.get_doc('Email Account', self.email_account)


@@ -300,3 +315,283 @@ def send_now(name):
def on_doctype_update(): def on_doctype_update():
"""Add index in `tabCommunication` for `(reference_doctype, reference_name)`""" """Add index in `tabCommunication` for `(reference_doctype, reference_name)`"""
frappe.db.add_index('Email Queue', ('status', 'send_after', 'priority', 'creation'), 'index_bulk_flush') frappe.db.add_index('Email Queue', ('status', 'send_after', 'priority', 'creation'), 'index_bulk_flush')

class QueueBuilder:
"""Builds Email Queue from the given data
"""
def __init__(self, recipients=None, sender=None, subject=None, message=None,
text_content=None, reference_doctype=None, reference_name=None,
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, reply_to=None, cc=None, bcc=None, message_id=None, in_reply_to=None,
send_after=None, expose_recipients=None, send_priority=1, communication=None,
read_receipt=None, queue_separately=False, is_notification=False,
add_unsubscribe_link=1, inline_images=None, header=None,
print_letterhead=False, with_container=False):
"""Add email to sending queue (Email Queue)

:param recipients: List of recipients.
:param sender: Email sender.
:param subject: Email subject.
:param message: Email message.
:param text_content: Text version of email message.
:param reference_doctype: Reference DocType of caller document.
:param reference_name: Reference name of caller document.
:param send_priority: Priority for Email Queue, default 1.
:param unsubscribe_method: URL method for unsubscribe. Default is `/api/method/frappe.email.queue.unsubscribe`.
:param unsubscribe_params: additional params for unsubscribed links. default are name, doctype, email
:param attachments: Attachments to be sent.
:param reply_to: Reply to be captured here (default inbox)
:param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To.
:param send_after: Send this email after the given datetime. If value is in integer, then `send_after` will be the automatically set to no of days from current date.
:param communication: Communication link to be set in Email Queue record
:param queue_separately: Queue each email separately
: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 inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id
:param header: Append header in email (boolean)
:param with_container: Wraps email inside styled container
"""

self._unsubscribe_method = unsubscribe_method
self._recipients = recipients
self._cc = cc
self._bcc = bcc
self._send_after = send_after
self._sender = sender
self._text_content = text_content
self._message = message
self._add_unsubscribe_link = add_unsubscribe_link
self._unsubscribe_message = unsubscribe_message
self._attachments = attachments

self._unsubscribed_user_emails = None
self._email_account = None

self.unsubscribe_params = unsubscribe_params
self.subject = subject
self.reference_doctype = reference_doctype
self.reference_name = reference_name
self.expose_recipients = expose_recipients
self.with_container = with_container
self.header = header
self.reply_to = reply_to
self.message_id = message_id
self.in_reply_to = in_reply_to
self.send_priority = send_priority
self.communication = communication
self.read_receipt = read_receipt
self.queue_separately = queue_separately
self.is_notification = is_notification
self.inline_images = inline_images
self.print_letterhead = print_letterhead

@property
def unsubscribe_method(self):
return self._unsubscribe_method or '/api/method/frappe.email.queue.unsubscribe'

def _get_emails_list(self, emails=None):
emails = split_emails(emails) if isinstance(emails, str) else (emails or [])
return [each for each in set(emails) if each]

@property
def recipients(self):
return self._get_emails_list(self._recipients)

@property
def cc(self):
return self._get_emails_list(self._cc)

@property
def bcc(self):
return self._get_emails_list(self._bcc)

@property
def send_after(self):
if isinstance(self._send_after, int):
return add_days(nowdate(), self._send_after)
return self._send_after

@property
def sender(self):
if not self._sender or self._sender == "Administrator":
email_account = self.get_outgoing_email_account()
return email_account.default_sender
return self._sender

def email_text_content(self):
unsubscribe_msg = self.unsubscribe_message()
unsubscribe_text_message = (unsubscribe_msg and unsubscribe_msg.text) or ''

if self._text_content:
return self._text_content + unsubscribe_text_message

try:
text_content = html2text(self._message)
except HTMLParser.HTMLParseError:
text_content = "See html attachment"
return text_content + unsubscribe_text_message

def email_html_content(self):
email_account = self.get_outgoing_email_account()
return get_formatted_html(self.subject, self._message, header=self.header,
email_account=email_account, unsubscribe_link=self.unsubscribe_message(),
with_container=self.with_container)

def should_include_unsubscribe_link(self):
return (self._add_unsubscribe_link == 1
and self.reference_doctype
and (self._unsubscribe_message or self.reference_doctype=="Newsletter"))

def unsubscribe_message(self):
if self.should_include_unsubscribe_link():
return get_unsubscribe_message(self._unsubscribe_message, self.expose_recipients)

def get_outgoing_email_account(self):
if self._email_account:
return self._email_account

self._email_account = EmailAccount.find_outgoing(
match_by_doctype=self.reference_doctype, match_by_email=self._sender, _raise_error=True)
return self._email_account

def get_unsubscribed_user_emails(self):
if self._unsubscribed_user_emails is not None:
return self._unsubscribed_user_emails

all_ids = tuple(set(self.recipients + self.cc))

unsubscribed = frappe.db.sql_list('''
SELECT
distinct email
from
`tabEmail Unsubscribe`
where
email in %(all_ids)s
and (
(
reference_doctype = %(reference_doctype)s
and reference_name = %(reference_name)s
)
or global_unsubscribe = 1
)
''', {
'all_ids': all_ids,
'reference_doctype': self.reference_doctype,
'reference_name': self.reference_name,
})

self._unsubscribed_user_emails = unsubscribed or []
return self._unsubscribed_user_emails

def final_recipients(self):
unsubscribed_emails = self.get_unsubscribed_user_emails()
return [mail_id for mail_id in self.recipients if mail_id not in unsubscribed_emails]

def final_cc(self):
unsubscribed_emails = self.get_unsubscribed_user_emails()
return [mail_id for mail_id in self.cc if mail_id not in unsubscribed_emails]

def get_attachments(self):
attachments = []
if self._attachments:
# store attachments with fid or print format details, to be attached on-demand later
for att in self._attachments:
if att.get('fid'):
attachments.append(att)
elif att.get("print_format_attachment") == 1:
if not att.get('lang', None):
att['lang'] = frappe.local.lang
att['print_letterhead'] = self.print_letterhead
attachments.append(att)
return attachments

def prepare_email_content(self):
mail = get_email(recipients=self.final_recipients(),
sender=self.sender,
subject=self.subject,
formatted=self.email_html_content(),
text_content=self.email_text_content(),
attachments=self._attachments,
reply_to=self.reply_to,
cc=self.final_cc(),
bcc=self.bcc,
email_account=self.get_outgoing_email_account(),
expose_recipients=self.expose_recipients,
inline_images=self.inline_images,
header=self.header)

mail.set_message_id(self.message_id, self.is_notification)
if self.read_receipt:
mail.msg_root["Disposition-Notification-To"] = self.sender
if self.in_reply_to:
mail.set_in_reply_to(self.in_reply_to)
return mail

def process(self, send_now=False):
"""Build and return the email queues those are created.

Sends email incase if it is requested to send now.
"""
final_recipients = self.final_recipients()
queue_separately = (final_recipients and self.queue_separately) or len(final_recipients) > 20
if not (final_recipients + self.final_cc()):
return []

email_queues = []
queue_data = self.as_dict(include_recipients=False)
if not queue_data:
return []

if not queue_separately:
recipients = list(set(final_recipients + self.final_cc() + self.bcc))
q = EmailQueue.new({**queue_data, **{'recipients': recipients}}, ignore_permissions=True)
email_queues.append(q)
else:
for r in final_recipients:
recipients = [r] if email_queues else list(set([r] + self.final_cc() + self.bcc))
q = EmailQueue.new({**queue_data, **{'recipients': recipients}}, ignore_permissions=True)
email_queues.append(q)

if send_now:
for doc in email_queues:
doc.send()
return email_queues

def as_dict(self, include_recipients=True):
email_account = self.get_outgoing_email_account()
email_account_name = email_account and email_account.is_exists_in_db() and email_account.name

mail = self.prepare_email_content()
try:
mail_to_string = cstr(mail.as_string())
except frappe.InvalidEmailAddressError:
# bad Email Address - don't add to queue
frappe.log_error('Invalid Email ID Sender: {0}, Recipients: {1}, \nTraceback: {2} '
.format(self.sender, ', '.join(self.final_recipients()), traceback.format_exc()),
'Email Not Sent'
)
return

d = {
'priority': self.send_priority,
'attachments': json.dumps(self.get_attachments()),
'message_id': mail.msg_root["Message-Id"].strip(" <>"),
'message': mail_to_string,
'sender': self.sender,
'reference_doctype': self.reference_doctype,
'reference_name': self.reference_name,
'add_unsubscribe_link': self._add_unsubscribe_link,
'unsubscribe_method': self.unsubscribe_method,
'unsubscribe_params': self.unsubscribe_params,
'expose_recipients': self.expose_recipients,
'communication': self.communication,
'send_after': self.send_after,
'show_as_cc': ",".join(self.final_cc()),
'show_as_bcc': ','.join(self.bcc),
'email_account': email_account_name or None
}

if include_recipients:
d['recipients'] = self.final_recipients()

return d

+ 0
- 1
frappe/email/doctype/newsletter/newsletter.py 查看文件

@@ -8,7 +8,6 @@ import frappe.utils
from frappe import throw, _ from frappe import throw, _
from frappe.website.website_generator import WebsiteGenerator from frappe.website.website_generator import WebsiteGenerator
from frappe.utils.verified_command import get_signed_params, verify_request from frappe.utils.verified_command import get_signed_params, verify_request
from frappe.email.queue import send
from frappe.email.doctype.email_group.email_group import add_subscribers from frappe.email.doctype.email_group.email_group import add_subscribers
from frappe.utils import parse_addr, now_datetime, markdown, validate_email_address from frappe.utils import parse_addr, now_datetime, markdown, validate_email_address




+ 2
- 250
frappe/email/queue.py 查看文件

@@ -3,257 +3,9 @@


from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import sys
from six.moves import html_parser as HTMLParser
import smtplib, quopri, json
from frappe import msgprint, _, safe_decode, safe_encode, enqueue
from frappe.email.smtp import SMTPServer
from frappe.email.doctype.email_account.email_account import EmailAccount
from frappe.email.email_body import get_email, get_formatted_html, add_attachment
from frappe import msgprint, _
from frappe.utils.verified_command import get_signed_params, verify_request from frappe.utils.verified_command import get_signed_params, verify_request
from html2text import html2text
from frappe.utils import get_url, nowdate, now_datetime, add_days, split_emails, cstr, cint
from rq.timeouts import JobTimeoutException
from six import text_type, string_types, PY3
from email.parser import Parser


class EmailLimitCrossedError(frappe.ValidationError): pass

def send(recipients=None, sender=None, subject=None, message=None, text_content=None, reference_doctype=None,
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, reply_to=None, cc=None, bcc=None, message_id=None, in_reply_to=None, send_after=None,
expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None,
queue_separately=False, is_notification=False, add_unsubscribe_link=1, inline_images=None,
header=None, print_letterhead=False, with_container=False):
"""Add email to sending queue (Email Queue)

:param recipients: List of recipients.
:param sender: Email sender.
:param subject: Email subject.
:param message: Email message.
:param text_content: Text version of email message.
:param reference_doctype: Reference DocType of caller document.
:param reference_name: Reference name of caller document.
:param send_priority: Priority for Email Queue, default 1.
:param unsubscribe_method: URL method for unsubscribe. Default is `/api/method/frappe.email.queue.unsubscribe`.
:param unsubscribe_params: additional params for unsubscribed links. default are name, doctype, email
:param attachments: Attachments to be sent.
:param reply_to: Reply to be captured here (default inbox)
:param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To.
:param send_after: Send this email after the given datetime. If value is in integer, then `send_after` will be the automatically set to no of days from current date.
:param communication: Communication link to be set in Email Queue record
:param now: Send immediately (don't send in the background)
:param queue_separately: Queue each email separately
: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 inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id
:param header: Append header in email (boolean)
:param with_container: Wraps email inside styled container
"""
if not unsubscribe_method:
unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe"

if not recipients and not cc:
return

if not cc:
cc = []
if not bcc:
bcc = []

if isinstance(recipients, string_types):
recipients = split_emails(recipients)

if isinstance(cc, string_types):
cc = split_emails(cc)

if isinstance(bcc, string_types):
bcc = split_emails(bcc)

if isinstance(send_after, int):
send_after = add_days(nowdate(), send_after)

email_account = EmailAccount.find_outgoing(
match_by_doctype=reference_doctype, match_by_email=sender, _raise_error=True)

if not sender or sender == "Administrator":
sender = email_account.default_sender

if not text_content:
try:
text_content = html2text(message)
except HTMLParser.HTMLParseError:
text_content = "See html attachment"

recipients = list(set(recipients))
cc = list(set(cc))

all_ids = tuple(recipients + cc)

unsubscribed = frappe.db.sql_list('''
SELECT
distinct email
from
`tabEmail Unsubscribe`
where
email in %(all_ids)s
and (
(
reference_doctype = %(reference_doctype)s
and reference_name = %(reference_name)s
)
or global_unsubscribe = 1
)
''', {
'all_ids': all_ids,
'reference_doctype': reference_doctype,
'reference_name': reference_name,
})

recipients = [r for r in recipients if r and r not in unsubscribed]

if cc:
cc = [r for r in cc if r and r not in unsubscribed]

if not recipients and not cc:
# Recipients may have been unsubscribed, exit quietly
return

email_text_context = text_content

should_append_unsubscribe = (add_unsubscribe_link
and reference_doctype
and (unsubscribe_message or reference_doctype=="Newsletter")
and add_unsubscribe_link==1)

unsubscribe_link = None
if should_append_unsubscribe:
unsubscribe_link = get_unsubscribe_message(unsubscribe_message, expose_recipients)
email_text_context += unsubscribe_link.text

email_content = get_formatted_html(subject, message,
email_account=email_account, header=header,
unsubscribe_link=unsubscribe_link, with_container=with_container)

# add to queue
add(recipients, sender, subject,
formatted=email_content,
text_content=email_text_context,
reference_doctype=reference_doctype,
reference_name=reference_name,
attachments=attachments,
reply_to=reply_to,
cc=cc,
bcc=bcc,
message_id=message_id,
in_reply_to=in_reply_to,
send_after=send_after,
send_priority=send_priority,
email_account=email_account,
communication=communication,
add_unsubscribe_link=add_unsubscribe_link,
unsubscribe_method=unsubscribe_method,
unsubscribe_params=unsubscribe_params,
expose_recipients=expose_recipients,
read_receipt=read_receipt,
queue_separately=queue_separately,
is_notification = is_notification,
inline_images = inline_images,
header=header,
now=now,
print_letterhead=print_letterhead)


def add(recipients, sender, subject, **kwargs):
"""Add to Email Queue"""
if kwargs.get('queue_separately') or len(recipients) > 20:
email_queue = None
for r in recipients:
if not email_queue:
email_queue = get_email_queue([r], sender, subject, **kwargs)
if kwargs.get('now'):
email_queue.send()
else:
duplicate = email_queue.get_duplicate([r])
duplicate.insert(ignore_permissions=True)

if kwargs.get('now'):
duplicate.send()

frappe.db.commit()
else:
email_queue = get_email_queue(recipients, sender, subject, **kwargs)
if kwargs.get('now'):
email_queue.send()

def get_email_queue(recipients, sender, subject, **kwargs):
'''Make Email Queue object'''
e = frappe.new_doc('Email Queue')
e.priority = kwargs.get('send_priority')
attachments = kwargs.get('attachments')
if attachments:
# store attachments with fid or print format details, to be attached on-demand later
_attachments = []
for att in attachments:
if att.get('fid'):
_attachments.append(att)
elif att.get("print_format_attachment") == 1:
if not att.get('lang', None):
att['lang'] = frappe.local.lang
att['print_letterhead'] = kwargs.get('print_letterhead')
_attachments.append(att)
e.attachments = json.dumps(_attachments)

try:
mail = get_email(recipients,
sender=sender,
subject=subject,
formatted=kwargs.get('formatted'),
text_content=kwargs.get('text_content'),
attachments=kwargs.get('attachments'),
reply_to=kwargs.get('reply_to'),
cc=kwargs.get('cc'),
bcc=kwargs.get('bcc'),
email_account=kwargs.get('email_account'),
expose_recipients=kwargs.get('expose_recipients'),
inline_images=kwargs.get('inline_images'),
header=kwargs.get('header'))

mail.set_message_id(kwargs.get('message_id'),kwargs.get('is_notification'))
if kwargs.get('read_receipt'):
mail.msg_root["Disposition-Notification-To"] = sender
if kwargs.get('in_reply_to'):
mail.set_in_reply_to(kwargs.get('in_reply_to'))

e.message_id = mail.msg_root["Message-Id"].strip(" <>")
e.message = cstr(mail.as_string())
e.sender = mail.sender

except frappe.InvalidEmailAddressError:
# bad Email Address - don't add to queue
import traceback
frappe.log_error('Invalid Email ID Sender: {0}, Recipients: {1}, \nTraceback: {2} '.format(mail.sender,
', '.join(mail.recipients), traceback.format_exc()), 'Email Not Sent')

recipients = list(set(recipients + kwargs.get('cc', []) + kwargs.get('bcc', [])))
email_account = kwargs.get('email_account')
email_account_name = email_account and email_account.is_exists_in_db() and email_account.name

e.set_recipients(recipients)
e.reference_doctype = kwargs.get('reference_doctype')
e.reference_name = kwargs.get('reference_name')
e.add_unsubscribe_link = kwargs.get("add_unsubscribe_link")
e.unsubscribe_method = kwargs.get('unsubscribe_method')
e.unsubscribe_params = kwargs.get('unsubscribe_params')
e.expose_recipients = kwargs.get('expose_recipients')
e.communication = kwargs.get('communication')
e.send_after = kwargs.get('send_after')
e.show_as_cc = ",".join(kwargs.get('cc', []))
e.show_as_bcc = ",".join(kwargs.get('bcc', []))
e.email_account = email_account_name or None
e.insert(ignore_permissions=True)
return e
from frappe.utils import get_url, now_datetime, cint


def get_emails_sent_this_month(): def get_emails_sent_this_month():
return frappe.db.sql(""" return frappe.db.sql("""


+ 5
- 4
frappe/email/smtp.py 查看文件

@@ -85,18 +85,19 @@ class SMTPServer:
SMTP = smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP SMTP = smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP


try: try:
self._session = SMTP(self.server, self.port)
if not self._session:
_session = SMTP(self.server, self.port)
if not _session:
frappe.msgprint(CONNECTION_FAILED, raise_exception=frappe.OutgoingEmailError) frappe.msgprint(CONNECTION_FAILED, raise_exception=frappe.OutgoingEmailError)


self.secure_session(self._session)
self.secure_session(_session)
if self.login and self.password: if self.login and self.password:
res = self._session.login(str(self.login or ""), str(self.password or ""))
res = _session.login(str(self.login or ""), str(self.password or ""))


# check if logged correctly # check if logged correctly
if res[0]!=235: if res[0]!=235:
frappe.msgprint(res[1], raise_exception=frappe.OutgoingEmailError) frappe.msgprint(res[1], raise_exception=frappe.OutgoingEmailError)


self._session = _session
return self._session return self._session


except smtplib.SMTPAuthenticationError as e: except smtplib.SMTPAuthenticationError as e:


+ 9
- 12
frappe/email/test_email_body.py 查看文件

@@ -7,8 +7,7 @@ from frappe import safe_decode
from frappe.email.receive import Email from frappe.email.receive import Email
from frappe.email.email_body import (replace_filename_with_cid, from frappe.email.email_body import (replace_filename_with_cid,
get_email, inline_style_in_html, get_header) get_email, inline_style_in_html, get_header)
from frappe.email.queue import get_email_queue
from frappe.email.doctype.email_queue.email_queue import SendMailContext
from frappe.email.doctype.email_queue.email_queue import SendMailContext, QueueBuilder
from six import PY3 from six import PY3


class TestEmailBody(unittest.TestCase): class TestEmailBody(unittest.TestCase):
@@ -50,27 +49,25 @@ This is the text version of this email
uni_chr1 = unichr(40960) uni_chr1 = unichr(40960)
uni_chr2 = unichr(1972) uni_chr2 = unichr(1972)


email = get_email_queue(
queue_doc = QueueBuilder(
recipients=['test@example.com'], recipients=['test@example.com'],
sender='me@example.com', sender='me@example.com',
subject='Test Subject', subject='Test Subject',
content='<h1>' + uni_chr1 + 'abcd' + uni_chr2 + '</h1>',
formatted='<h1>' + uni_chr1 + 'abcd' + uni_chr2 + '</h1>',
text_content='whatever')
mail_ctx = SendMailContext(queue_doc = email)
message='<h1>' + uni_chr1 + 'abcd' + uni_chr2 + '</h1>',
text_content='whatever').process()[0]
mail_ctx = SendMailContext(queue_doc = queue_doc)
result = mail_ctx.build_message(recipient_email = 'test@test.com') result = mail_ctx.build_message(recipient_email = 'test@test.com')
self.assertTrue(b"<h1>=EA=80=80abcd=DE=B4</h1>" in result) self.assertTrue(b"<h1>=EA=80=80abcd=DE=B4</h1>" in result)


def test_prepare_message_returns_cr_lf(self): def test_prepare_message_returns_cr_lf(self):
email = get_email_queue(
queue_doc = QueueBuilder(
recipients=['test@example.com'], recipients=['test@example.com'],
sender='me@example.com', sender='me@example.com',
subject='Test Subject', subject='Test Subject',
content='<h1>\n this is a test of newlines\n' + '</h1>',
formatted='<h1>\n this is a test of newlines\n' + '</h1>',
text_content='whatever')
message='<h1>\n this is a test of newlines\n' + '</h1>',
text_content='whatever').process()[0]


mail_ctx = SendMailContext(queue_doc = email)
mail_ctx = SendMailContext(queue_doc = queue_doc)
result = safe_decode(mail_ctx.build_message(recipient_email='test@test.com')) result = safe_decode(mail_ctx.build_message(recipient_email='test@test.com'))


if PY3: if PY3:


+ 7
- 6
frappe/tests/test_email.py 查看文件

@@ -143,7 +143,8 @@ class TestEmail(unittest.TestCase):
self.assertEqual(len(queue_recipients), 2) self.assertEqual(len(queue_recipients), 2)


def test_unsubscribe(self): def test_unsubscribe(self):
from frappe.email.queue import unsubscribe, send
from frappe.email.queue import unsubscribe
from frappe.email.doctype.email_queue.email_queue import QueueBuilder
unsubscribe(doctype="User", name="Administrator", email="test@example.com") unsubscribe(doctype="User", name="Administrator", email="test@example.com")


self.assertTrue(frappe.db.get_value("Email Unsubscribe", self.assertTrue(frappe.db.get_value("Email Unsubscribe",
@@ -152,11 +153,11 @@ class TestEmail(unittest.TestCase):


before = frappe.db.sql("""select count(name) from `tabEmail Queue` where status='Not Sent'""")[0][0] before = frappe.db.sql("""select count(name) from `tabEmail Queue` where status='Not Sent'""")[0][0]


send(recipients=['test@example.com', 'test1@example.com'],
sender="admin@example.com",
reference_doctype='User', reference_name="Administrator",
subject='Testing Email Queue', message='This is mail is queued!', unsubscribe_message="Unsubscribe")
builder = QueueBuilder(recipients=['test@example.com', 'test1@example.com'],
sender="admin@example.com",
reference_doctype='User', reference_name="Administrator",
subject='Testing Email Queue', message='This is mail is queued!', unsubscribe_message="Unsubscribe")
builder.process()
# this is sent async (?) # this is sent async (?)


email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Not Sent'""", email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Not Sent'""",


+ 1
- 1
frappe/utils/error.py 查看文件

@@ -215,7 +215,7 @@ def raise_error_on_no_output(error_message, error_type=None, keep_quiet=None):
>>> @raise_error_on_no_output("Ingradients missing") >>> @raise_error_on_no_output("Ingradients missing")
... def get_indradients(_raise_error=1): return ... def get_indradients(_raise_error=1): return
... ...
>>> get_indradients()
>>> get_ingradients()
`Exception Name`: Ingradients missing `Exception Name`: Ingradients missing
""" """
def decorator_raise_error_on_no_output(func): def decorator_raise_error_on_no_output(func):


正在加载...
取消
保存