Procházet zdrojové kódy

Email cc in headers if expose recipients (#2441)

* move email cc to header

* [test fixes] move email cc to header

* email cc additional tests

* email cc cleanup and unsubsribe link test

* email cc permit footer based cc with expose_recipient and tests + ui
version-14
robert schouten před 8 roky
committed by Rushabh Mehta
rodič
revize
efa86c6e31
12 změnil soubory, kde provedl 458 přidání a 135 odebrání
  1. +3
    -3
      frappe/__init__.py
  2. +3
    -3
      frappe/core/doctype/communication/email.py
  3. +2
    -2
      frappe/email/doctype/email_queue/email_queue.js
  4. +107
    -4
      frappe/email/doctype/email_queue/email_queue.json
  5. +1
    -1
      frappe/email/doctype/email_queue/email_queue.py
  6. +0
    -0
      frappe/email/doctype/email_queue_recipient/__init__.py
  7. +122
    -0
      frappe/email/doctype/email_queue_recipient/email_queue_recipient.json
  8. +10
    -0
      frappe/email/doctype/email_queue_recipient/email_queue_recipient.py
  9. +7
    -3
      frappe/email/doctype/newsletter/test_newsletter.py
  10. +6
    -5
      frappe/email/email_body.py
  11. +108
    -95
      frappe/email/queue.py
  12. +89
    -19
      frappe/tests/test_email.py

+ 3
- 3
frappe/__init__.py Zobrazit soubor

@@ -355,11 +355,11 @@ def get_request_header(key, default=None):
:param default: Default value."""
return request.headers.get(key, default)

def sendmail(recipients=(), sender="", subject="No Subject", message="No Message",
def sendmail(recipients=[], sender="", subject="No Subject", message="No Message",
as_markdown=False, delayed=True, reference_doctype=None, reference_name=None,
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, content=None, doctype=None, name=None, reply_to=None,
cc=(), show_as_cc=(), in_reply_to=None, send_after=None, expose_recipients=False,
cc=[], in_reply_to=None, send_after=None, expose_recipients=None,
send_priority=1, communication=None, retry=1, now=None):
"""Send email using user's default **Email Account** or global default **Email Account**.

@@ -396,7 +396,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
subject=subject, message=message,
reference_doctype = doctype or reference_doctype, reference_name = name or reference_name,
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message,
attachments=attachments, reply_to=reply_to, cc=cc, show_as_cc=show_as_cc, in_reply_to=in_reply_to,
attachments=attachments, reply_to=reply_to, cc=cc, in_reply_to=in_reply_to,
send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority,
communication=communication, now=now)



+ 3
- 3
frappe/core/doctype/communication/email.py Zobrazit soubor

@@ -136,9 +136,9 @@ def _notify(doc, print_html=None, print_format=None, attachments=None,
unsubscribe_message = ""

frappe.sendmail(
recipients=(recipients or []) + (cc or []),
show_as_cc=(cc or []),
expose_recipients=True,
recipients=(recipients or []),
cc=(cc or []),
expose_recipients="header",
sender=doc.sender,
reply_to=doc.incoming_email_account,
subject=doc.subject,


+ 2
- 2
frappe/email/doctype/email_queue/email_queue.js Zobrazit soubor

@@ -3,7 +3,7 @@

frappe.ui.form.on("Email Queue", {
refresh: function(frm) {
if (frm.doc.status==="Not Sent") {
if (["Not Sent","Partially Sent"].indexOf(frm.doc.status)!=-1) {
frm.add_custom_button("Send Now", function() {
frappe.call({
method: 'frappe.email.doctype.email_queue.email_queue.send_now',
@@ -17,7 +17,7 @@ frappe.ui.form.on("Email Queue", {
});
}

if (frm.doc.status==="Error") {
if (["Error","Partially Errored"].indexOf(frm.doc.status)!=-1) {
frm.add_custom_button("Retry Sending", function() {
frm.call({
method: "retry_sending",


+ 107
- 4
frappe/email/doctype/email_queue/email_queue.json Zobrazit soubor

@@ -48,19 +48,47 @@
"collapsible": 0,
"columns": 0,
"fieldname": "recipient",
"fieldtype": "Data",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recipient",
"length": 0,
"no_copy": 0,
"options": "Email",
"options": "Email Queue Recipient",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "show_as_cc",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Show as cc",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -331,6 +359,81 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "unsubscribe_param",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Unsubscribe Param",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "unsubscribe_method",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Unsubscribe Method",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "expose_recipients",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Expose Recipients",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@@ -344,7 +447,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-11-17 16:45:02.296617",
"modified": "2016-12-13 20:43:56.976928",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Queue",


+ 1
- 1
frappe/email/doctype/email_queue/email_queue.py Zobrazit soubor

@@ -22,7 +22,7 @@ class EmailQueue(Document):
@frappe.whitelist()
def retry_sending(name):
doc = frappe.get_doc("Email Queue", name)
if doc and doc.status == "Error":
if doc and (doc.status == "Error" or doc.status == "Partially Errored"):
doc.status = "Not Sent"
doc.save(ignore_permissions=True)



+ 0
- 0
frappe/email/doctype/email_queue_recipient/__init__.py Zobrazit soubor


+ 122
- 0
frappe/email/doctype/email_queue_recipient/email_queue_recipient.json Zobrazit soubor

@@ -0,0 +1,122 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-12-08 12:01:07.993900",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 0,
"engine": "InnoDB",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "recipient",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Recipient",
"length": 0,
"no_copy": 0,
"options": "Email",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Not Sent",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "\nNot Sent\nSending\nSent\nError\nExpired",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "error",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Error",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-12-08 14:05:33.578240",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Queue Recipient",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
}

+ 10
- 0
frappe/email/doctype/email_queue_recipient/email_queue_recipient.py Zobrazit soubor

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe.model.document import Document

class EmailQueueRecipient(Document):
pass

+ 7
- 3
frappe/email/doctype/newsletter/test_newsletter.py Zobrazit soubor

@@ -20,21 +20,25 @@ class TestNewsletter(unittest.TestCase):

def test_send(self):
self.send_newsletter()
self.assertEquals(len(frappe.get_all("Email Queue")), 3)
self.assertEquals(len(frappe.get_all("Email Queue")), 1)
self.assertEquals(len(frappe.get_all("Email Queue Recipient")), 3)

def test_unsubscribe(self):
# test unsubscribe
self.send_newsletter()

from frappe.email.queue import flush
flush(from_test=True)
email = unquote(frappe.local.flags.signed_query_string.split("email=")[1].split("&")[0])

unsubscribe(email, "_Test Email Group")

self.send_newsletter()
self.assertEquals(len(frappe.get_all("Email Queue")), 2)
self.assertEquals(len(frappe.get_all("Email Queue")), 1)
self.assertEquals(len(frappe.get_all("Email Queue Recipient")), 2)

def send_newsletter(self):
frappe.db.sql("delete from `tabEmail Queue`")
frappe.db.sql("delete from `tabEmail Queue Recipient`")
frappe.delete_doc("Newsletter", "_Test Newsletter")
newsletter = frappe.get_doc({
"doctype": "Newsletter",


+ 6
- 5
frappe/email/email_body.py Zobrazit soubor

@@ -11,10 +11,10 @@ import email.utils

def get_email(recipients, sender='', msg='', subject='[No Subject]',
text_content = None, footer=None, print_html=None, formatted=None, attachments=None,
content=None, reply_to=None, cc=(), email_account=None):
content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None):
"""send an html email as multipart with attachments and all"""
content = content or msg
emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account)
emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account, expose_recipients=expose_recipients)

if not content.strip().startswith("<"):
content = markdown(content)
@@ -35,7 +35,7 @@ class EMail:
Also provides a clean way to add binary `FileData` attachments
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):
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
Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
@@ -51,6 +51,7 @@ class EMail:
self.reply_to = reply_to or sender
self.recipients = recipients
self.subject = subject
self.expose_recipients = expose_recipients

self.msg_root = MIMEMultipart('mixed')
self.msg_multipart = MIMEMultipart('alternative')
@@ -195,10 +196,10 @@ class EMail:
headers = {
"Subject": strip(self.subject),
"From": self.sender,
"To": ', '.join(self.recipients),
"To": ', '.join(self.recipients) if self.expose_recipients=="header" else "<!--recipient-->",
"Date": email.utils.formatdate(),
"Reply-To": self.reply_to if self.reply_to else None,
"CC": ', '.join(self.cc) if self.cc else None,
"CC": ', '.join(self.cc) if self.cc and self.expose_recipients=="header" else None,
'X-Frappe-Site': get_url(),
}



+ 108
- 95
frappe/email/queue.py Zobrazit soubor

@@ -18,8 +18,8 @@ class EmailLimitCrossedError(frappe.ValidationError): pass

def send(recipients=None, sender=None, subject=None, message=None, reference_doctype=None,
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, reply_to=None, cc=(), show_as_cc=(), in_reply_to=None, send_after=None,
expose_recipients=False, send_priority=1, communication=None, now=False):
attachments=None, reply_to=None, cc=[], in_reply_to=None, send_after=None,
expose_recipients=None, send_priority=1, communication=None, now=False):
"""Add email to sending queue (Email Queue)

:param recipients: List of recipients.
@@ -41,7 +41,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
if not unsubscribe_method:
unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe"

if not recipients:
if not recipients and not cc:
return

if isinstance(recipients, basestring):
@@ -74,54 +74,34 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc

recipients = [r for r in list(set(recipients)) if r and r not in unsubscribed]

for email in recipients:
email_content = formatted
email_text_context = text_content

if reference_doctype and (unsubscribe_message or reference_doctype=="Newsletter"):
unsubscribe_link = get_unsubscribe_link(
reference_doctype=reference_doctype,
reference_name=reference_name,
email=email,
recipients=recipients,
expose_recipients=expose_recipients,
unsubscribe_method=unsubscribe_method,
unsubscribe_params=unsubscribe_params,
unsubscribe_message=unsubscribe_message,
show_as_cc=show_as_cc
)

email_content = email_content.replace("<!--unsubscribe link here-->", unsubscribe_link.html)
email_text_context += unsubscribe_link.text

# show as cc
cc_message = ""
if email in show_as_cc:
cc_message = _("This email was sent to you as CC")

email_content = email_content.replace("<!-- cc message -->", cc_message)
email_text_context = cc_message + "\n" + email_text_context
# add to queue
email_queue = add(email, sender, subject, email_content, email_text_context, reference_doctype,
reference_name, attachments, reply_to, cc, in_reply_to, send_after, send_priority, email_account=email_account, communication=communication)
email_content = formatted
email_text_context = text_content

if now:
send_one(email_queue.name, now=True)
if reference_doctype and (unsubscribe_message or reference_doctype=="Newsletter"):
unsubscribe_link = get_unsubscribe_message(unsubscribe_message, expose_recipients)
email_content = email_content.replace("<!--unsubscribe link here-->", unsubscribe_link.html)
email_text_context += unsubscribe_link.text

# add to queue
email_queue = add(recipients, sender, subject, email_content, email_text_context, reference_doctype,
reference_name, attachments, reply_to, cc, in_reply_to, send_after, send_priority, email_account=email_account, communication=communication,
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, expose_recipients=expose_recipients)
if now:
send_one(email_queue.name, now=True)


def add(email, sender, subject, formatted, text_content=None,
def add(recipients, sender, subject, formatted, text_content=None,
reference_doctype=None, reference_name=None, attachments=None, reply_to=None,
cc=(), in_reply_to=None, send_after=None, send_priority=1, email_account=None,
communication=None):
cc=[], in_reply_to=None, send_after=None, send_priority=1, email_account=None,
communication=None, unsubscribe_method=None, unsubscribe_params=None, expose_recipients=None):
"""Add to Email Queue"""
e = frappe.new_doc('Email Queue')
e.recipient = email
e.priority = send_priority

try:
mail = get_email(email, sender=sender, formatted=formatted, subject=subject,
mail = get_email(recipients, sender=sender, formatted=formatted, subject=subject,
text_content=text_content, attachments=attachments, reply_to=reply_to,
cc=cc, email_account=email_account)
cc=cc, email_account=email_account, expose_recipients=expose_recipients)

if in_reply_to:
mail.set_in_reply_to(in_reply_to)
@@ -134,11 +114,18 @@ def add(email, sender, subject, formatted, text_content=None,
# bad email id - don't add to queue
return

e.set("recipient", [])
for r in recipients + cc:
e.append("recipient",{"recipient":r})
e.reference_doctype = reference_doctype
e.reference_name = reference_name
e.unsubscribe_method = unsubscribe_method
e.unsubscribe_params = unsubscribe_params
e.expose_recipients = expose_recipients
e.communication = communication
e.send_after = send_after
e.db_insert()
e.show_as_cc = ",".join(cc)
e.insert(ignore_permissions=True)

return e

@@ -170,43 +157,23 @@ def get_emails_sent_this_month():
return frappe.db.sql("""select count(name) from `tabEmail Queue` where
status='Sent' and MONTH(creation)=MONTH(CURDATE())""")[0][0]

def get_unsubscribe_link(reference_doctype, reference_name,
email, recipients, expose_recipients, show_as_cc,
unsubscribe_method, unsubscribe_params, unsubscribe_message):

email_sent_to = recipients if expose_recipients else [email]
email_sent_cc = ", ".join([e for e in email_sent_to if e in show_as_cc])
email_sent_to = ", ".join([e for e in email_sent_to if e not in show_as_cc])

if email_sent_cc:
email_sent_message = _("This email was sent to {0} and copied to {1}").format(email_sent_to, email_sent_cc)
else:
email_sent_message = _("This email was sent to {0}").format(email_sent_to)

def get_unsubscribe_message(unsubscribe_message, expose_recipients):
if not unsubscribe_message:
unsubscribe_message = _("Unsubscribe from this list")

unsubscribe_url = get_unsubcribed_url(reference_doctype, reference_name, email,
unsubscribe_method, unsubscribe_params)

html = """<div style="margin: 15px auto; padding: 0px 7px; text-align: center; color: #8d99a6;">
{email}
<!--cc message-->
<p style="margin: 15px auto;">
<a href="{unsubscribe_url}" style="color: #8d99a6; text-decoration: underline;
<a href="<!--unsubscribe url-->" style="color: #8d99a6; text-decoration: underline;
target="_blank">{unsubscribe_message}
</a>
</p>
</div>""".format(
unsubscribe_url = unsubscribe_url,
email=email_sent_message,
unsubscribe_message=unsubscribe_message
)

text = "\n{email}\n\n{unsubscribe_message}: {unsubscribe_url}".format(
email=email_sent_message,
unsubscribe_message=unsubscribe_message,
unsubscribe_url=unsubscribe_url
)
</div>""".format(unsubscribe_message=unsubscribe_message)
if expose_recipients == "footer":
text = "\n<!--cc message-->"
else:
text = ""
text += "\n\n{unsubscribe_message}: <!--unsubscribe url-->".format(unsubscribe_message=unsubscribe_message)

return frappe._dict({
"html": html,
@@ -281,7 +248,7 @@ def make_cache_queue():
cache = frappe.cache()

emails = frappe.db.sql('''select name from `tabEmail Queue`
where status='Not Sent' and (send_after is null or send_after < %(now)s)
where (status='Not Sent' or status='Partially Sent') and (send_after is null or send_after < %(now)s)
order by priority desc, creation asc
limit 500''', { 'now': now_datetime() })

@@ -294,28 +261,21 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
'''Send Email Queue with given smtpserver'''

email = frappe.db.sql('''select name, status, communication,
message, sender, recipient, reference_doctype
message, sender, reference_doctype, reference_name, unsubscribe_param, unsubscribe_method, expose_recipients, show_as_cc
from `tabEmail Queue` where name=%s for update''', email, as_dict=True)[0]

if from_test:
# called from specific test, just set it as sent
frappe.db.set_value('Email Queue', email.name, 'status', 'Sent')
return

if frappe.flags.in_test:
# call form general test, add the sent email to flags and quit
frappe.flags.sent_mail = email.message
return
recipients_list = frappe.db.sql('''select name, recipient, status from `tabEmail Queue Recipient` where parent=%s''',email.name,as_dict=1)

if frappe.are_emails_muted():
frappe.msgprint(_("Emails are muted"))
return

if email.status != 'Not Sent':
if email.status not in ('Not Sent','Partially Sent') :
# rollback to release lock and return
frappe.db.rollback()
return


frappe.db.sql("""update `tabEmail Queue` set status='Sending', modified=%s where name=%s""",
(now_datetime(), email.name), auto_commit=auto_commit)

@@ -323,14 +283,32 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)

try:
if auto_commit:
if not frappe.flags.in_test:
if not smtpserver: smtpserver = SMTPServer()
smtpserver.setup_email_account(email.reference_doctype)
smtpserver.sess.sendmail(email.sender, email.recipient, encode(email.message))

frappe.db.sql("""update `tabEmail Queue` set status='Sent', modified=%s where name=%s""",
(now_datetime(), email.name), auto_commit=auto_commit)
for recipient in recipients_list:
if recipient.status != "Not Sent":
continue

message = prepare_message(email, recipient.recipient, recipients_list)
if not frappe.flags.in_test:
smtpserver.sess.sendmail(email.sender, recipient.recipient, encode(message))

recipient.status = "Sent"
frappe.db.sql("""update `tabEmail Queue Recipient` set status='Sent', modified=%s where name=%s""",
(now_datetime(), recipient.name), auto_commit=auto_commit)

#if all are sent set status
if any("Sent" == s.status for s in recipients_list):
frappe.db.sql("""update `tabEmail Queue` set status='Sent', modified=%s where name=%s""",
(now_datetime(), email.name), auto_commit=auto_commit)
else:
frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s
where name=%s""", ("No recipients to send to", email.name), auto_commit=auto_commit)
if frappe.flags.in_test:
frappe.flags.sent_mail = message
return
if email.communication:
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)

@@ -341,8 +319,13 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
JobTimeoutException):

# bad connection/timeout, retry later
frappe.db.sql("""update `tabEmail Queue` set status='Not Sent', modified=%s where name=%s""",
(now_datetime(), email.name), auto_commit=auto_commit)

if any("Sent" == s.status for s in recipients_list):
frappe.db.sql("""update `tabEmail Queue` set status='Partially Sent', modified=%s where name=%s""",
(now_datetime(), email.name), auto_commit=auto_commit)
else:
frappe.db.sql("""update `tabEmail Queue` set status='Not Sent', modified=%s where name=%s""",
(now_datetime(), email.name), auto_commit=auto_commit)

if email.communication:
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)
@@ -353,8 +336,12 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
except Exception, e:
frappe.db.rollback()

frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s
where name=%s""", (unicode(e), email.name), auto_commit=auto_commit)
if any("Sent" == s.status for s in recipients_list):
frappe.db.sql("""update `tabEmail Queue` set status='Partially Errored', error=%s where name=%s""",
(unicode(e), email.name), auto_commit=auto_commit)
else:
frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s
where name=%s""", (unicode(e), email.name), auto_commit=auto_commit)

if email.communication:
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)
@@ -366,12 +353,38 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
# log to Error Log
log('frappe.email.queue.flush', unicode(e))

def prepare_message(email, recipient, recipients_list):
message = email.message
if 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,
email.unsubscribe_method, email.unsubscribe_params)
message = message.replace("<!--unsubscribe url-->", unsubscribe_url)

if email.expose_recipients == "header":
pass
else:
if email.expose_recipients == "footer":
if isinstance(email.show_as_cc, basestring):
email.show_as_cc = email.show_as_cc.split(",")
email_sent_to = [r.recipient for r in recipients_list]
email_sent_cc = ", ".join([e for e in email_sent_to if e in email.show_as_cc])
email_sent_to = ", ".join([e for e in email_sent_to if e not in email.show_as_cc])

if email_sent_cc:
email_sent_message = _("This email was sent to {0} and copied to {1}").format(email_sent_to,email_sent_cc)
else:
email_sent_message = _("This email was sent to {0}").format(email_sent_to)
message = message.replace("<!--cc message-->", email_sent_message)

message = message.replace("<!--recipient-->", recipient)
return message

def clear_outbox():
"""Remove low priority older than 31 days in Outbox and expire mails not sent for 7 days.

Called daily via scheduler."""
frappe.db.sql("""delete from `tabEmail Queue` where priority=0 and
datediff(now(), modified) > 31""")
frappe.db.sql("""delete q, r from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r where q.name = r.parent and q.priority=0 and
datediff(now(), q.modified) > 31""")

frappe.db.sql("""update `tabEmail Queue` set status='Expired'
where datediff(curdate(), modified) > 7 and status='Not Sent'""")
frappe.db.sql("""update `tabEmail Queue` as q, `tabEmail Queue Recipient` as r set q.status='Expired', r.status='Expired'
where q.name = r.parent and datediff(curdate(), q.modified) > 7 and q.status='Not Sent' and r.status='Not Sent'""")

+ 89
- 19
frappe/tests/test_email.py Zobrazit soubor

@@ -3,7 +3,7 @@

from __future__ import unicode_literals

import unittest, frappe
import unittest, frappe, re

from frappe.test_runner import make_test_records
make_test_records("User")
@@ -13,6 +13,7 @@ class TestEmail(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabEmail Unsubscribe`""")
frappe.db.sql("""delete from `tabEmail Queue`""")
frappe.db.sql("""delete from `tabEmail Queue Recipient`""")

def test_email_queue(self, send_after=None):
frappe.sendmail(recipients = ['test@example.com', 'test1@example.com'],
@@ -21,37 +22,103 @@ class TestEmail(unittest.TestCase):
subject='Testing Queue', message='This mail is queued!',
unsubscribe_message="Unsubscribe", send_after=send_after)

email_queue = frappe.db.sql("""select * from `tabEmail Queue` where status='Not Sent'""", as_dict=1)
self.assertEquals(len(email_queue), 2)
self.assertTrue('test@example.com' in [d['recipient'] for d in email_queue])
self.assertTrue('test1@example.com' in [d['recipient'] for d in email_queue])
self.assertTrue('Unsubscribe' in email_queue[0]['message'])
email_queue = frappe.db.sql("""select name,message from `tabEmail Queue` where status='Not Sent'""", as_dict=1)
self.assertEquals(len(email_queue), 1)
queue_recipients = [r.recipient for r in frappe.db.sql("""SELECT recipient FROM `tabEmail Queue Recipient`
WHERE status='Not Sent'""", as_dict=1)]
self.assertTrue('test@example.com' in queue_recipients)
self.assertTrue('test1@example.com' in queue_recipients)
self.assertEquals(len(queue_recipients), 2)
self.assertTrue('<!--unsubscribe url-->' in email_queue[0]['message'])

def test_send_after(self):
self.test_email_queue(send_after = 1)
from frappe.email.queue import flush
flush(from_test=True)
email_queue = frappe.db.sql("""select * from `tabEmail Queue` where status='Sent'""", as_dict=1)
email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Sent'""", as_dict=1)
self.assertEquals(len(email_queue), 0)

def test_flush(self):
self.test_email_queue()
from frappe.email.queue import flush
flush(from_test=True)
email_queue = frappe.db.sql("""select * from `tabEmail Queue` where status='Sent'""", as_dict=1)
self.assertEquals(len(email_queue), 2)
self.assertTrue('test@example.com' in [d['recipient'] for d in email_queue])
self.assertTrue('test1@example.com' in [d['recipient'] for d in email_queue])
email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Sent'""", as_dict=1)
self.assertEquals(len(email_queue), 1)
queue_recipients = [r.recipient for r in frappe.db.sql("""select recipient from `tabEmail Queue Recipient`
where status='Sent'""", as_dict=1)]
self.assertTrue('test@example.com' in queue_recipients)
self.assertTrue('test1@example.com' in queue_recipients)
self.assertEquals(len(queue_recipients), 2)
self.assertTrue('Unsubscribe' in frappe.flags.sent_mail)

def test_cc_header(self):
#test if sending with cc's makes it into header
frappe.sendmail(recipients=['test@example.com'],
cc=['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", expose_recipients="header")
email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Not Sent'""", as_dict=1)
self.assertEquals(len(email_queue), 1)
queue_recipients = [r.recipient for r in frappe.db.sql("""select recipient from `tabEmail Queue Recipient`
where status='Not Sent'""", as_dict=1)]
self.assertTrue('test@example.com' in queue_recipients)
self.assertTrue('test1@example.com' in queue_recipients)

message = frappe.db.sql("""select message from `tabEmail Queue`
where status='Not Sent'""", as_dict=1)[0].message
self.assertTrue('To: test@example.com' in message)
self.assertTrue('CC: test1@example.com' in message)

def test_cc_footer(self):
#test if sending with cc's makes it into header
frappe.sendmail(recipients=['test@example.com'],
cc=['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", expose_recipients="footer", now=True)
email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Sent'""", as_dict=1)
self.assertEquals(len(email_queue), 1)
queue_recipients = [r.recipient for r in frappe.db.sql("""select recipient from `tabEmail Queue Recipient`
where status='Sent'""", as_dict=1)]
self.assertTrue('test@example.com' in queue_recipients)
self.assertTrue('test1@example.com' in queue_recipients)

self.assertTrue('This email was sent to test@example.com and copied to test1@example.com' in frappe.flags.sent_mail)

def test_expose(self):
frappe.sendmail(recipients=['test@example.com'],
cc=['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", now=True)
email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Sent'""", as_dict=1)
self.assertEquals(len(email_queue), 1)
queue_recipients = [r.recipient for r in frappe.db.sql("""select recipient from `tabEmail Queue Recipient`
where status='Sent'""", as_dict=1)]
self.assertTrue('test@example.com' in queue_recipients)
self.assertTrue('test1@example.com' in queue_recipients)
message = frappe.db.sql("""select message from `tabEmail Queue`
where status='Sent'""", as_dict=1)[0].message
self.assertTrue('<!--recipient-->' in message)

frappe.local.flags.signed_query_string = re.search('(?<=/api/method/frappe.email.queue.unsubscribe\?).*(?=\n)', frappe.flags.sent_mail).group(0)
from frappe.utils.verified_command import verify_request
self.assertTrue(verify_request())

def test_expired(self):
self.test_email_queue()
frappe.db.sql("update `tabEmail Queue` set modified=DATE_SUB(curdate(), interval 8 day)")
from frappe.email.queue import clear_outbox
clear_outbox()
email_queue = frappe.db.sql("""select * from `tabEmail Queue` where status='Expired'""", as_dict=1)
self.assertEquals(len(email_queue), 2)
self.assertTrue('test@example.com' in [d['recipient'] for d in email_queue])
self.assertTrue('test1@example.com' in [d['recipient'] for d in email_queue])
email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Expired'""", as_dict=1)
self.assertEquals(len(email_queue), 1)
queue_recipients = [r.recipient for r in frappe.db.sql("""select recipient from `tabEmail Queue Recipient`
where parent = %s""",email_queue[0].name, as_dict=1)]
self.assertTrue('test@example.com' in queue_recipients)
self.assertTrue('test1@example.com' in queue_recipients)
self.assertEquals(len(queue_recipients), 2)

def test_unsubscribe(self):
from frappe.email.queue import unsubscribe, send
@@ -69,12 +136,15 @@ class TestEmail(unittest.TestCase):

# this is sent async (?)

email_queue = frappe.db.sql("""select * from `tabEmail Queue` where status='Not Sent'""",
email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Not Sent'""",
as_dict=1)
self.assertEquals(len(email_queue), before + 1)
self.assertFalse('test@example.com' in [d['recipient'] for d in email_queue])
self.assertTrue('test1@example.com' in [d['recipient'] for d in email_queue])
self.assertTrue('Unsubscribe' in email_queue[0]['message'])
queue_recipients = [r.recipient for r in frappe.db.sql("""select recipient from `tabEmail Queue Recipient`
where status='Not Sent'""", as_dict=1)]
self.assertFalse('test@example.com' in queue_recipients)
self.assertTrue('test1@example.com' in queue_recipients)
self.assertEquals(len(queue_recipients), 1)
self.assertTrue('Unsubscribe' in frappe.flags.sent_mail)

def test_email_queue_limit(self):
from frappe.email.queue import send, EmailLimitCrossedError


Načítá se…
Zrušit
Uložit