Browse Source

Merge pull request #1808 from rmehta/email-queue

[rename] Bulk Email to Email Queue
version-14
Rushabh Mehta 9 years ago
committed by GitHub
parent
commit
64fdb2da43
34 changed files with 483 additions and 482 deletions
  1. +11
    -13
      frappe/__init__.py
  2. +5
    -5
      frappe/commands/utils.py
  3. +1
    -1
      frappe/config/core.py
  4. +1
    -2
      frappe/core/doctype/communication/comment.py
  5. +3
    -3
      frappe/core/doctype/communication/communication.py
  6. +5
    -5
      frappe/core/doctype/communication/email.py
  7. +9
    -6
      frappe/core/doctype/user/user.py
  8. +1
    -1
      frappe/desk/doctype/event/event.py
  9. +1
    -2
      frappe/desk/page/chat/chat.py
  10. +0
    -1
      frappe/email/doctype/bulk_email/README.md
  11. +0
    -4
      frappe/email/doctype/bulk_email/__init__.py
  12. +0
    -307
      frappe/email/doctype/bulk_email/bulk_email.json
  13. +0
    -12
      frappe/email/doctype/bulk_email/test_bulk_email.py
  14. +2
    -3
      frappe/email/doctype/email_account/email_account.py
  15. +6
    -6
      frappe/email/doctype/email_account/test_email_account.py
  16. +2
    -3
      frappe/email/doctype/email_alert/email_alert.py
  17. +13
    -13
      frappe/email/doctype/email_alert/test_email_alert.py
  18. +0
    -0
      frappe/email/doctype/email_queue/__init__.py
  19. +4
    -1
      frappe/email/doctype/email_queue/email_queue.js
  20. +308
    -0
      frappe/email/doctype/email_queue/email_queue.json
  21. +8
    -8
      frappe/email/doctype/email_queue/email_queue.py
  22. +1
    -1
      frappe/email/doctype/email_queue/email_queue_list.js
  23. +12
    -0
      frappe/email/doctype/email_queue/test_email_queue.py
  24. +33
    -33
      frappe/email/queue.py
  25. +4
    -4
      frappe/hooks.py
  26. +2
    -2
      frappe/limits.py
  27. +1
    -0
      frappe/patches.txt
  28. +3
    -3
      frappe/patches/v5_0/rename_ref_type_fieldnames.py
  29. +1
    -1
      frappe/patches/v5_0/v4_to_v5.py
  30. +4
    -0
      frappe/patches/v7_0/rename_bulk_email_to_email_queue.py
  31. +1
    -1
      frappe/patches/v7_0/update_send_after_in_bulk_email.py
  32. +1
    -1
      frappe/templates/includes/comments/comments.py
  33. +1
    -1
      frappe/tests/test_db.py
  34. +39
    -39
      frappe/tests/test_email.py

+ 11
- 13
frappe/__init__.py View File

@@ -7,7 +7,6 @@ globals attached to frappe module
from __future__ import unicode_literals

from werkzeug.local import Local, release_local
from functools import wraps
import os, importlib, inspect, json

# public
@@ -252,7 +251,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
:param raise_exception: [optional] Raise given exception and show message.
:param as_table: [optional] If `msg` is a list of lists, render as HTML table.
"""
from utils import cstr, encode
from utils import encode

out = _dict(message=msg)

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

def sendmail(recipients=(), sender="", subject="No Subject", message="No Message",
as_markdown=False, bulk=False, reference_doctype=None, reference_name=None,
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=(), message_id=None, in_reply_to=None, as_bulk=False, send_after=None, expose_recipients=False,
bulk_priority=1, communication=None):
cc=(), show_as_cc=(), message_id=None, in_reply_to=None, send_after=None, expose_recipients=False,
send_priority=1, communication=None):
"""Send email using user's default **Email Account** or global default **Email Account**.


@@ -368,8 +367,8 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
:param subject: Email Subject.
:param message: (or `content`) Email Content.
:param as_markdown: Convert content markdown to HTML.
:param bulk: Send via scheduled email sender **Bulk Email**. Don't send immediately.
:param bulk_priority: Priority for bulk email, default 1.
:param delayed: Send via scheduled email sender **Email Queue**. Don't send immediately. Default is true
:param send_priority: Priority for Email Queue, default 1.
:param reference_doctype: (or `doctype`) Append as communication to this DocType.
:param reference_name: (or `name`) Append as communication to this document name.
:param unsubscribe_method: Unsubscribe url with options email, doctype, name. e.g. `/api/method/unsubscribe`
@@ -380,17 +379,17 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
:param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To.
:param send_after: Send after the given datetime.
:param expose_recipients: Display all recipients in the footer message - "This email was sent to"
:param communication: Communication link to be set in Bulk Email record
:param communication: Communication link to be set in Email Queue record
"""

if bulk or as_bulk:
import frappe.email.bulk
frappe.email.bulk.send(recipients=recipients, sender=sender,
if delayed:
import frappe.email.queue
frappe.email.queue.send(recipients=recipients, sender=sender,
subject=subject, message=content or 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, message_id=message_id, in_reply_to=in_reply_to,
send_after=send_after, expose_recipients=expose_recipients, bulk_priority=bulk_priority, communication=communication)
send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, communication=communication)
else:
import frappe.email
if as_markdown:
@@ -918,7 +917,6 @@ def import_doc(path, ignore_links=False, ignore_insert=False, insert=False):
def copy_doc(doc, ignore_no_copy=True):
""" No_copy fields also get copied."""
import copy
from frappe.model import optional_fields, default_fields

def remove_no_copy_fields(d):
for df in d.meta.get("fields", {"no_copy": 1}):


+ 5
- 5
frappe/commands/utils.py View File

@@ -146,16 +146,16 @@ def execute(context, method, args=None, kwargs=None):
print json.dumps(ret)


@click.command('add-bulk-email')
@click.command('add-to-email-queue')
@click.argument('email')
@pass_context
def add_bulk_email(context, email):
"Add an email to the Bulk Email queue"
def add_to_email_queue(context, email):
"Add an email to the Email Queue"
site = get_site(context)
with frappe.init_site(site):
frappe.connect()
kwargs = json.loads(email)
kwargs['as_bulk'] = True
kwargs['delayed'] = True
frappe.sendmail(**kwargs)
frappe.db.commit()

@@ -408,7 +408,6 @@ def get_version():
print "{0} {1}".format(m, module.__version__)

commands = [
add_bulk_email,
build,
build_website,
clear_cache,
@@ -433,4 +432,5 @@ commands = [
sync_www,
watch,
_bulk_rename,
add_to_email_queue,
]

+ 1
- 1
frappe/config/core.py View File

@@ -48,7 +48,7 @@ def get_data():
},
{
"type": "doctype",
"name": "Bulk Email",
"name": "Email Queue",
"description": _("Background Email Queue"),
},
{


+ 1
- 2
frappe/core/doctype/communication/comment.py View File

@@ -104,8 +104,7 @@ def notify_mentions(doc):
recipients=recipients,
sender=frappe.session.user,
subject=subject,
message=message,
bulk=True
message=message
)

def get_comments_from_parent(doc):


+ 3
- 3
frappe/core/doctype/communication/communication.py View File

@@ -161,7 +161,7 @@ class Communication(Document):

def notify(self, print_html=None, print_format=None, attachments=None,
recipients=None, cc=None, fetched_from_email_account=False):
"""Calls a delayed task 'sendmail' that enqueus email in Bulk Email queue
"""Calls a delayed task 'sendmail' that enqueus email in Email Queue queue

:param print_html: Send given value as HTML attachment
:param print_format: Attach print format of parent document
@@ -193,9 +193,9 @@ class Communication(Document):
frappe.local.flags.commit = True

def set_delivery_status(self, commit=False):
'''Look into the status of Bulk Email linked to this Communication and set the Delivery Status of this Communication'''
'''Look into the status of Email Queue linked to this Communication and set the Delivery Status of this Communication'''
delivery_status = None
status_counts = Counter(frappe.db.sql_list('''select status from `tabBulk Email` where communication=%s''', self.name))
status_counts = Counter(frappe.db.sql_list('''select status from `tabEmail Queue` where communication=%s''', self.name))

if status_counts.get('Not Sent') or status_counts.get('Sending'):
delivery_status = 'Sending'


+ 5
- 5
frappe/core/doctype/communication/email.py View File

@@ -8,7 +8,7 @@ from email.utils import formataddr, parseaddr
from frappe.utils import (get_url, get_formatted_email, cint,
validate_email_add, split_emails, time_diff_in_seconds)
from frappe.utils.file_manager import get_file
from frappe.email.bulk import check_bulk_limit
from frappe.email.queue import check_email_limit
from frappe.utils.scheduler import log
import frappe.email.smtp
import MySQLdb
@@ -89,7 +89,7 @@ def validate_email(doc):

def notify(doc, print_html=None, print_format=None, attachments=None,
recipients=None, cc=None, fetched_from_email_account=False):
"""Calls a delayed task 'sendmail' that enqueus email in Bulk Email queue
"""Calls a delayed task 'sendmail' that enqueus email in Email Queue queue

:param print_html: Send given value as HTML attachment
:param print_format: Attach print format of parent document
@@ -109,7 +109,7 @@ def notify(doc, print_html=None, print_format=None, attachments=None,
doc._notify(print_html=print_html, print_format=print_format, attachments=attachments,
recipients=recipients, cc=cc)
else:
check_bulk_limit(list(set(doc.sent_email_addresses)))
check_email_limit(list(set(doc.sent_email_addresses)))
enqueue(sendmail, queue="default", timeout=300, event="sendmail",
communication_name=doc.name,
print_html=print_html, print_format=print_format, attachments=attachments,
@@ -133,7 +133,7 @@ def _notify(doc, print_html=None, print_format=None, attachments=None,
attachments=doc.attachments,
message_id=doc.name,
unsubscribe_message=_("Leave this conversation"),
bulk=True,
delayed=True,
communication=doc.name
)

@@ -191,7 +191,7 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None)

:param print_html: Send given value as HTML attachment.
:param print_format: Attach print format of parent document."""
view_link = frappe.utils.cint(frappe.db.get_value("Print Settings", "Print Settings", "attach_view_link"))

if print_format and view_link:


+ 9
- 6
frappe/core/doctype/user/user.py View File

@@ -189,19 +189,21 @@ class User(Document):
(self.first_name and " " or '') + (self.last_name or '')

def password_reset_mail(self, link):
self.send_login_mail(_("Password Reset"), "templates/emails/password_reset.html", {"link": link})
self.send_login_mail(_("Password Reset"),
"templates/emails/password_reset.html", {"link": link}, now=True)

def password_update_mail(self, password):
self.send_login_mail(_("Password Update"), "templates/emails/password_update.html", {"new_password": password})
self.send_login_mail(_("Password Update"),
"templates/emails/password_update.html", {"new_password": password}, now=True)

def send_welcome_mail_to_user(self):
from frappe.utils import random_string, get_url
from frappe.utils import get_url

link = self.reset_password()
self.send_login_mail(_("Verify Your Account"), "templates/emails/new_user.html",
{"link": link, "site_url": get_url()})

def send_login_mail(self, subject, template, add_args):
def send_login_mail(self, subject, template, add_args, now=None):
"""send mail with login details"""
from frappe.utils.user import get_user_fullname
from frappe.utils import get_url
@@ -226,7 +228,8 @@ class User(Document):
sender = frappe.session.user not in STANDARD_USERS and get_formatted_email(frappe.session.user) or None

frappe.sendmail(recipients=self.email, sender=sender, subject=subject,
message=frappe.get_template(template).render(args), as_bulk=self.flags.delay_emails)
message=frappe.get_template(template).render(args),
delayed=now if now!=None else self.flags.delay_emails)

def a_system_manager_should_exist(self):
if not self.get_other_system_managers():
@@ -607,7 +610,7 @@ def notifify_admin_access_to_system_manager(login_manager=None):
)

frappe.sendmail(recipients=get_system_managers(), subject=_("Administrator Logged In"),
message=message, bulk=True)
message=message)

def extract_mentions(txt):
"""Find all instances of @username in the string.


+ 1
- 1
frappe/desk/doctype/event/event.py View File

@@ -66,7 +66,7 @@ def send_event_digest():
+ frappe._("Daily Event Digest is sent for Calendar Events where reminders are set.")+'</p>'

frappe.sendmail(recipients=user.email, subject=frappe._("Upcoming Events for Today"),
content = text, bulk=True)
content = text)

@frappe.whitelist()
def get_events(start, end, user=None, for_reminder=False):


+ 1
- 2
frappe/desk/page/chat/chat.py View File

@@ -131,7 +131,6 @@ def _notify(contact, txt, subject=None):
"from": get_fullname(frappe.session.user),
"message": txt,
"link": get_url()
}),
bulk=True)
}))
except frappe.OutgoingEmailError:
pass

+ 0
- 1
frappe/email/doctype/bulk_email/README.md View File

@@ -1 +0,0 @@
Emails to be sent asynchronously via the scheduler. Emails remain in Bulk Email for a month before they are deleted (to check mail quotas)

+ 0
- 4
frappe/email/doctype/bulk_email/__init__.py View File

@@ -1,4 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals

+ 0
- 307
frappe/email/doctype/bulk_email/bulk_email.json View File

@@ -1,307 +0,0 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
"beta": 0,
"creation": "2012-08-02 15:17:28",
"custom": 0,
"description": "Bulk Email records.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "sender",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sender",
"length": 0,
"no_copy": 0,
"options": "Email",
"permlevel": 0,
"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": "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,
"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": "message",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"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,
"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": "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,
"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": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"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": "reference_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference DocName",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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": "communication",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Communication",
"length": 0,
"no_copy": 0,
"options": "Communication",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "send_after",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Send After",
"length": 0,
"no_copy": 1,
"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,
"default": "1",
"fieldname": "priority",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Priority",
"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
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-envelope",
"idx": 1,
"in_create": 1,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-05-26 06:00:18.596285",
"modified_by": "Administrator",
"module": "Email",
"name": "Bulk Email",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "DESC",
"track_seen": 0
}

+ 0
- 12
frappe/email/doctype/bulk_email/test_bulk_email.py View File

@@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals

import frappe
import unittest

# test_records = frappe.get_test_records('Bulk Email')

class TestBulkEmail(unittest.TestCase):
pass

+ 2
- 3
frappe/email/doctype/email_account/email_account.py View File

@@ -355,8 +355,7 @@ class EmailAccount(Document):
reference_name = communication.reference_name,
message_id = communication.name,
in_reply_to = email.mail.get("Message-Id"), # send back the Message-Id as In-Reply-To
unsubscribe_message = _("Leave this conversation"),
bulk=True)
unsubscribe_message = _("Leave this conversation"))

def get_unreplied_notification_emails(self):
"""Return list of emails listed"""
@@ -398,7 +397,7 @@ def notify_unreplied():
# if status is still open
frappe.sendmail(recipients=email_account.get_unreplied_notification_emails(),
content=comm.content, subject=comm.subject, doctype= comm.reference_doctype,
name=comm.reference_name, bulk=True)
name=comm.reference_name)

# update flag
comm.db_set("unread_notification_sent", 1)


+ 6
- 6
frappe/email/doctype/email_account/test_email_account.py View File

@@ -42,9 +42,9 @@ class TestEmailAccount(unittest.TestCase):
comm = frappe.get_doc("Communication", {"sender": "test_sender@example.com"})
comm.db_set("creation", datetime.now() - timedelta(seconds = 30 * 60))

frappe.db.sql("delete from `tabBulk Email`")
frappe.db.sql("delete from `tabEmail Queue`")
notify_unreplied()
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": comm.reference_doctype,
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": comm.reference_doctype,
"reference_name": comm.reference_name, "status":"Not Sent"}))

def test_incoming_with_attach(self):
@@ -102,13 +102,13 @@ class TestEmailAccount(unittest.TestCase):
make(subject = "test-mail-000", content="test mail 000", recipients="test_receiver@example.com",
send_email=True, sender="test_sender@example.com")

mail = email.message_from_string(frappe.get_last_doc("Bulk Email").message)
mail = email.message_from_string(frappe.get_last_doc("Email Queue").message)
self.assertTrue("test-mail-000" in mail.get("Subject"))

def test_sendmail(self):
frappe.flags.sent_mail = None
frappe.sendmail(sender="test_sender@example.com", recipients="test_recipient@example.com",
content="test mail 001", subject="test-mail-001")
content="test mail 001", subject="test-mail-001", delayed=False)

sent_mail = email.message_from_string(frappe.flags.sent_mail)
self.assertTrue("test-mail-001" in sent_mail.get("Subject"))
@@ -119,7 +119,7 @@ class TestEmailAccount(unittest.TestCase):
content="test mail 001", subject="test-mail-002", doctype="Email Account",
name="_Test Email Account 1", print_format="Standard", send_email=True)

sent_mail = email.message_from_string(frappe.get_last_doc("Bulk Email").message)
sent_mail = email.message_from_string(frappe.get_last_doc("Email Queue").message)
self.assertTrue("test-mail-002" in sent_mail.get("Subject"))

def test_threading(self):
@@ -131,7 +131,7 @@ class TestEmailAccount(unittest.TestCase):
recipients="test_receiver@example.com", sender="test@example.com",
send_email=True)["name"]

sent_mail = email.message_from_string(frappe.get_last_doc("Bulk Email").message)
sent_mail = email.message_from_string(frappe.get_last_doc("Email Queue").message)

with open(os.path.join(os.path.dirname(__file__), "test_mails", "reply-1.raw"), "r") as f:
raw = f.read()


+ 2
- 3
frappe/email/doctype/email_alert/email_alert.py View File

@@ -32,7 +32,7 @@ class EmailAlert(Document):
frappe.throw(_("The Condition '{0}' is invalid").format(self.condition))

def validate_forbidden_types(self):
forbidden_document_types = ("Bulk Email",)
forbidden_document_types = ("Email Queue",)
if (self.document_type in forbidden_document_types
or frappe.get_meta(self.document_type).istable):
# currently email alerts don't work on child tables as events are not fired for each record of child table
@@ -151,8 +151,7 @@ def evaluate_alert(doc, alert, event):
subject = frappe.render_template(alert.subject, context)

frappe.sendmail(recipients=recipients, subject=subject,
message= frappe.render_template(alert.message, context),
bulk=True, reference_doctype = doc.doctype, reference_name = doc.name,
message= frappe.render_template(alert.message, context), reference_doctype = doc.doctype, reference_name = doc.name,
attachments = [frappe.attach_print(doc.doctype, doc.name)] if alert.attach_print else None)

def get_context(doc):


+ 13
- 13
frappe/email/doctype/email_alert/test_email_alert.py View File

@@ -9,7 +9,7 @@ test_records = frappe.get_test_records('Email Alert')

class TestEmailAlert(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabBulk Email`""")
frappe.db.sql("""delete from `tabEmail Queue`""")
frappe.set_user("test1@example.com")

def tearDown(self):
@@ -22,14 +22,14 @@ class TestEmailAlert(unittest.TestCase):
communication.content = "test"
communication.insert(ignore_permissions=True)

self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Communication",
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": "Communication",
"reference_name": communication.name, "status":"Not Sent"}))
frappe.db.sql("""delete from `tabBulk Email`""")
frappe.db.sql("""delete from `tabEmail Queue`""")

communication.content = "test 2"
communication.save()

self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Communication",
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": "Communication",
"reference_name": communication.name, "status":"Not Sent"}))

def test_condition(self):
@@ -39,13 +39,13 @@ class TestEmailAlert(unittest.TestCase):
event.starts_on = "2014-06-06 12:00:00"
event.insert()

self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event",
self.assertFalse(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

event.event_type = "Public"
event.save()

self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event",
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

def test_invalid_condition(self):
@@ -72,19 +72,19 @@ class TestEmailAlert(unittest.TestCase):
event.starts_on = "2014-06-06 12:00:00"
event.insert()

self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event",
self.assertFalse(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

event.subject = "test 1"
event.save()

self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event",
self.assertFalse(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

event.description = "test"
event.save()

self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event",
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

def test_date_changed(self):
@@ -94,23 +94,23 @@ class TestEmailAlert(unittest.TestCase):
event.starts_on = "2014-01-01 12:00:00"
event.insert()

self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event",
self.assertFalse(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True)

# not today, so no alert
self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event",
self.assertFalse(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

event.starts_on = frappe.utils.add_days(frappe.utils.nowdate(), 2) + " 12:00:00"
event.save()

self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event",
self.assertFalse(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True)

# today so show alert
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event",
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
"reference_name": event.name, "status":"Not Sent"}))

+ 0
- 0
frappe/email/doctype/email_queue/__init__.py View File


frappe/email/doctype/bulk_email/bulk_email.js → frappe/email/doctype/email_queue/email_queue.js View File

@@ -1,4 +1,7 @@
frappe.ui.form.on("Bulk Email", {
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on("Email Queue", {
refresh: function(frm) {
if (frm.doc.status==="Not Sent") {
frm.add_custom_button("Send Now", function() {

+ 308
- 0
frappe/email/doctype/email_queue/email_queue.json View File

@@ -0,0 +1,308 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
"beta": 0,
"creation": "2012-08-02 15:17:28",
"custom": 0,
"description": "Email Queue records.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "sender",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Sender",
"length": 0,
"no_copy": 0,
"options": "Email",
"permlevel": 0,
"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": "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,
"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": "message",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"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,
"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": "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,
"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": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"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": "reference_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reference DocName",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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": "communication",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Communication",
"length": 0,
"no_copy": 0,
"options": "Communication",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "send_after",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Send After",
"length": 0,
"no_copy": 1,
"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,
"default": "1",
"fieldname": "priority",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Priority",
"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
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-envelope",
"idx": 1,
"image_view": 0,
"in_create": 1,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-06-22 12:23:10.621244",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Queue",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "DESC",
"track_seen": 0
}

frappe/email/doctype/bulk_email/bulk_email.py → frappe/email/doctype/email_queue/email_queue.py View File

@@ -1,28 +1,28 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# -*- 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
from frappe.email.bulk import send_one
from frappe.utils import now_datetime
from frappe.email.queue import send_one

class BulkEmail(Document):
class EmailQueue(Document):
pass

@frappe.whitelist()
def retry_sending(name):
doc = frappe.get_doc("Bulk Email", name)
doc = frappe.get_doc("Email Queue", name)
if doc and doc.status == "Error":
doc.status = "Not Sent"
doc.save(ignore_permissions=True)

@frappe.whitelist()
def send_now(name):
doc = frappe.get_doc("Bulk Email", name)
doc = frappe.get_doc("Email Queue", name)
send_one(doc, now=True)

def on_doctype_update():
"""Add index in `tabCommunication` for `(reference_doctype, reference_name)`"""
frappe.db.add_index('Bulk Email', ('status', 'send_after', 'priority', 'creation'), 'index_bulk_flush')
frappe.db.add_index('Email Queue', ('status', 'send_after', 'priority', 'creation'), 'index_bulk_flush')

frappe/email/doctype/bulk_email/bulk_email_list.js → frappe/email/doctype/email_queue/email_queue_list.js View File

@@ -1,4 +1,4 @@
frappe.listview_settings['Bulk Email'] = {
frappe.listview_settings['Email Queue'] = {
get_indicator: function(doc) {
colour = {'Sent': 'green', 'Sending': 'blue', 'Not Sent': 'grey', 'Error': 'red', 'Expired': 'orange'};
return [__(doc.status), colour[doc.status], "status,=," + doc.status];

+ 12
- 0
frappe/email/doctype/email_queue/test_email_queue.py View File

@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals

import frappe
import unittest

# test_records = frappe.get_test_records('Email Queue')

class TestEmailQueue(unittest.TestCase):
pass

frappe/email/bulk.py → frappe/email/queue.py View File

@@ -14,13 +14,13 @@ from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split
from rq.timeouts import JobTimeoutException
from frappe.utils.scheduler import log

class BulkLimitCrossedError(frappe.ValidationError): pass
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=(), message_id=None, in_reply_to=None, send_after=None,
expose_recipients=False, bulk_priority=1, communication=None):
"""Add email to sending queue (Bulk Email)
expose_recipients=False, send_priority=1, communication=None):
"""Add email to sending queue (Email Queue)

:param recipients: List of recipients.
:param sender: Email sender.
@@ -28,18 +28,18 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
:param message: Email message.
:param reference_doctype: Reference DocType of caller document.
:param reference_name: Reference name of caller document.
:param bulk_priority: Priority for bulk email, default 1.
:param unsubscribe_method: URL method for unsubscribe. Default is `/api/method/frappe.email.bulk.unsubscribe`.
: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 message_id: Used for threading. If a reply is received to this email, Message-Id is sent back as In-Reply-To in received email.
: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 Bulk Email record
:param communication: Communication link to be set in Email Queue record
"""
if not unsubscribe_method:
unsubscribe_method = "/api/method/frappe.email.bulk.unsubscribe"
unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe"

if not recipients:
return
@@ -54,7 +54,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
if not sender or sender == "Administrator":
sender = email_account.default_sender

check_bulk_limit(recipients)
check_email_limit(recipients)

formatted = get_formatted_html(subject, message, email_account=email_account)

@@ -104,15 +104,15 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc

# add to queue
add(email, sender, subject, email_content, email_text_context, reference_doctype,
reference_name, attachments, reply_to, cc, message_id, in_reply_to, send_after, bulk_priority, email_account=email_account, communication=communication)
reference_name, attachments, reply_to, cc, message_id, in_reply_to, send_after, send_priority, email_account=email_account, communication=communication)

def add(email, sender, subject, formatted, text_content=None,
reference_doctype=None, reference_name=None, attachments=None, reply_to=None,
cc=(), message_id=None, in_reply_to=None, send_after=None, bulk_priority=1, email_account=None, communication=None):
"""add to bulk mail queue"""
e = frappe.new_doc('Bulk Email')
cc=(), message_id=None, in_reply_to=None, send_after=None, send_priority=1, email_account=None, communication=None):
"""Add to Email Queue"""
e = frappe.new_doc('Email Queue')
e.recipient = email
e.priority = bulk_priority
e.priority = send_priority

try:
mail = get_email(email, sender=sender, formatted=formatted, subject=subject,
@@ -137,8 +137,8 @@ def add(email, sender, subject, formatted, text_content=None,
e.send_after = send_after
e.insert(ignore_permissions=True)

def check_bulk_limit(recipients):
# if using settings from site_config.json, check bulk limit
def check_email_limit(recipients):
# if using settings from site_config.json, check email limit
# No limit for own email settings
smtp_server = SMTPServer()

@@ -147,14 +147,14 @@ def check_bulk_limit(recipients):
or frappe.flags.in_test):

# get count of mails sent this month
this_month = frappe.db.sql("""select count(name) from `tabBulk Email` where
this_month = frappe.db.sql("""select count(name) from `tabEmail Queue` where
status='Sent' and MONTH(creation)=MONTH(CURDATE())""")[0][0]

monthly_bulk_mail_limit = frappe.conf.get('monthly_bulk_mail_limit') or 500
monthly_email_limit = frappe.conf.get('monthly_email_limit') or 500

if (this_month + len(recipients)) > monthly_bulk_mail_limit:
throw(_("Cannot send this email. You have crossed the sending limit of {0} emails for this month.").format(monthly_bulk_mail_limit),
BulkLimitCrossedError)
if (this_month + len(recipients)) > monthly_email_limit:
throw(_("Cannot send this email. You have crossed the sending limit of {0} emails for this month.").format(monthly_email_limit),
EmailLimitCrossedError)

def get_unsubscribe_link(reference_doctype, reference_name,
email, recipients, expose_recipients, show_as_cc,
@@ -241,21 +241,21 @@ def return_unsubscribed_page(email, doctype, name):
def flush(from_test=False):
"""flush email queue, every time: called from scheduler"""
# additional check
check_bulk_limit([])
check_email_limit([])

auto_commit = not from_test
if frappe.are_emails_muted():
msgprint(_("Emails are muted"))
from_test = True

frappe.db.sql("""update `tabBulk Email` set status='Expired'
frappe.db.sql("""update `tabEmail Queue` set status='Expired'
where datediff(curdate(), creation) > 7 and status='Not Sent'""", auto_commit=auto_commit)

smtpserver = SMTPServer()

for i in xrange(500):
# don't use for update here, as it leads deadlocks
email = frappe.db.sql('''select * from `tabBulk Email`
email = frappe.db.sql('''select * from `tabEmail Queue`
where status='Not Sent' and (send_after is null or send_after < %(now)s)
order by priority desc, creation asc
limit 1''', { 'now': now_datetime() }, as_dict=True)
@@ -272,15 +272,15 @@ def flush(from_test=False):
# frappe.db.commit()

def send_one(email, smtpserver=None, auto_commit=True, now=False):
'''Send bulk email with given smtpserver'''
'''Send Email Queue with given smtpserver'''

status = frappe.db.sql('''select status from `tabBulk Email` where name=%s for update''', email.name)[0][0]
status = frappe.db.sql('''select status from `tabEmail Queue` where name=%s for update''', email.name)[0][0]
if status != 'Not Sent':
# rollback to release lock and return
frappe.db.rollback()
return

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

if email.communication:
@@ -292,7 +292,7 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False):
smtpserver.setup_email_account(email.reference_doctype)
smtpserver.sess.sendmail(email.sender, email.recipient, encode(email.message))

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

if email.communication:
@@ -305,7 +305,7 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False):
JobTimeoutException):

# bad connection/timeout, retry later
frappe.db.sql("""update `tabBulk Email` set status='Not Sent', modified=%s where name=%s""",
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:
@@ -317,7 +317,7 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False):
except Exception, e:
frappe.db.rollback()

frappe.db.sql("""update `tabBulk Email` set status='Error', error=%s
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:
@@ -328,14 +328,14 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False):

else:
# log to scheduler log
log('frappe.email.bulk.flush', unicode(e))
log('frappe.email.queue.flush', unicode(e))

def clear_outbox():
"""Remove mails older than 31 days in Outbox. Called daily via scheduler."""
frappe.db.sql("""delete from `tabBulk Email` where
frappe.db.sql("""delete from `tabEmail Queue` where
datediff(now(), creation) > 31""")

def prevent_bulk_email_delete(doc, method):
def prevent_email_queue_delete(doc, method):
from frappe.limits import get_limits
if frappe.session.user != 'Administrator' and get_limits().get('block_bulk_email_delete'):
frappe.throw(_('Only Administrator can delete Bulk Email'))
frappe.throw(_('Only Administrator can delete Email Queue'))

+ 4
- 4
frappe/hooks.py View File

@@ -99,8 +99,8 @@ doc_events = {
"User": {
"validate": "frappe.utils.user.validate_user_limit"
},
"Bulk Email": {
"on_trash": "frappe.email.bulk.prevent_bulk_email_delete"
"Email Queue": {
"on_trash": "frappe.email.queue.prevent_email_queue_delete"
},
"*": {
"after_insert": "frappe.email.doctype.email_alert.email_alert.trigger_email_alerts",
@@ -124,7 +124,7 @@ doc_events = {

scheduler_events = {
"all": [
"frappe.email.bulk.flush",
"frappe.email.queue.flush",
"frappe.email.doctype.email_account.email_account.pull",
"frappe.email.doctype.email_account.email_account.notify_unreplied",
"frappe.utils.error.collect_error_snapshots",
@@ -132,7 +132,7 @@ scheduler_events = {
'frappe.model.utils.list_settings.sync_list_settings'
],
"daily": [
"frappe.email.bulk.clear_outbox",
"frappe.email.queue.clear_outbox",
"frappe.desk.notifications.clear_notifications",
"frappe.core.doctype.scheduler_log.scheduler_log.set_old_logs_as_seen",
"frappe.desk.doctype.event.event.send_event_digest",


+ 2
- 2
frappe/limits.py View File

@@ -35,7 +35,7 @@ def check_if_expired():
# if expired, stop user from logging in
expires_on = formatdate(get_limits().get("expiry"))
support_email = get_limits().get("support_email") or _("your provider")
frappe.throw(_("""Your subscription expired on {0}.
To extend please send an email to {1}""").format(expires_on, support_email),
SiteExpiredError)
@@ -72,7 +72,7 @@ def get_expiry_message():
def get_limits():
limits = frappe.get_conf().get("limits") or {}
day = frappe.utils.add_months(frappe.utils.today(), -1)
limits["bulk_count"] = frappe.db.count("Bulk Email", filters={'creation': ['>', day]})
limits["emails_sent"] = frappe.db.count("Email Queue", filters={'creation': ['>', day]})
return limits




+ 1
- 0
frappe/patches.txt View File

@@ -15,6 +15,7 @@ execute:frappe.reload_doc('custom', 'doctype', 'property_setter') #2014-12-31-1
execute:frappe.reload_doc('core', 'doctype', 'patch_log') #2016-10-31
execute:frappe.reload_doctype("File") # 2015-10-19
execute:frappe.reload_doc('core', 'doctype', 'error_snapshot')
frappe.patches.v7_0.rename_bulk_email_to_email_queue

execute:frappe.db.sql("alter table `tabSessions` modify `user` varchar(255), engine=InnoDB")
execute:frappe.db.sql("delete from `tabDocField` where parent='0'")


+ 3
- 3
frappe/patches/v5_0/rename_ref_type_fieldnames.py View File

@@ -6,14 +6,14 @@ import frappe

def execute():
try:
frappe.db.sql("alter table `tabBulk Email` change `ref_docname` `reference_name` varchar(255)")
frappe.db.sql("alter table `tabEmail Queue` change `ref_docname` `reference_name` varchar(255)")
except Exception, e:
if e.args[0] not in (1054, 1060):
raise

try:
frappe.db.sql("alter table `tabBulk Email` change `ref_doctype` `reference_doctype` varchar(255)")
frappe.db.sql("alter table `tabEmail Queue` change `ref_doctype` `reference_doctype` varchar(255)")
except Exception, e:
if e.args[0] not in (1054, 1060):
raise
frappe.reload_doctype("Bulk Email")
frappe.reload_doctype("Email Queue")

+ 1
- 1
frappe/patches/v5_0/v4_to_v5.py View File

@@ -9,7 +9,7 @@ def execute():
("desk", ("feed", "event", "event_role", "todo", "note")),
("custom", ("custom_field", "custom_script", "customize_form",
"customize_form_field", "property_setter")),
("email", ("bulk_email", "email_alert", "email_alert_recipient", "standard_reply")),
("email", ("email_queue", "email_alert", "email_alert_recipient", "standard_reply")),
("geo", ("country", "currency")),
("print", ("letter_head", "print_format", "print_settings"))
)


+ 4
- 0
frappe/patches/v7_0/rename_bulk_email_to_email_queue.py View File

@@ -0,0 +1,4 @@
import frappe

def execute():
frappe.rename_doc('DocType', 'Bulk Email', 'Email Queue')

+ 1
- 1
frappe/patches/v7_0/update_send_after_in_bulk_email.py View File

@@ -2,4 +2,4 @@ import frappe
from frappe.utils import now_datetime

def execute():
frappe.db.sql('update `tabBulk Email` set send_after=%s where send_after is null', now_datetime())
frappe.db.sql('update `tabEmail Queue` set send_after=%s where send_after is null', now_datetime())

+ 1
- 1
frappe/templates/includes/comments/comments.py View File

@@ -49,7 +49,7 @@ def add_comment(args=None):
message += "<p><a href='{0}/{1}' style='font-size: 80%'>{2}</a></p>".format(frappe.utils.get_request_site_address(),
page_name, _("View it in your browser"))

from frappe.email.bulk import send
from frappe.email.queue import send

send(recipients=recipients,
subject = _("New comment on {0} {1}").format(doc.doctype, doc.name),


+ 1
- 1
frappe/tests/test_db.py View File

@@ -26,4 +26,4 @@ class TestDB(unittest.TestCase):

def test_multiple_queries(self):
# implicit commit
self.assertRaises(frappe.SQLError, frappe.db.sql, """select name from `tabUser`; truncate `tabBulk Email`""")
self.assertRaises(frappe.SQLError, frappe.db.sql, """select name from `tabUser`; truncate `tabEmail Queue`""")

+ 39
- 39
frappe/tests/test_email.py View File

@@ -12,81 +12,81 @@ make_test_records("Email Account")
class TestEmail(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabEmail Unsubscribe`""")
frappe.db.sql("""delete from `tabBulk Email`""")
frappe.db.sql("""delete from `tabEmail Queue`""")

def test_send(self):
from frappe.email import sendmail
sendmail('test@example.com', subject='Test Mail', msg="Test Content")

def test_bulk(self, send_after=None):
from frappe.email.bulk import send
def test_email_queue(self, send_after=None):
from frappe.email.queue import send
send(recipients = ['test@example.com', 'test1@example.com'],
sender="admin@example.com",
reference_doctype='User', reference_name='Administrator',
subject='Testing Bulk', message='This is a bulk mail!', send_after=send_after)
subject='Testing Queue', message='This mail is queued!', send_after=send_after)

bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Not Sent'""", as_dict=1)
self.assertEquals(len(bulk), 2)
self.assertTrue('test@example.com' in [d['recipient'] for d in bulk])
self.assertTrue('test1@example.com' in [d['recipient'] for d in bulk])
self.assertTrue('Unsubscribe' in bulk[0]['message'])
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'])

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

def test_send_after(self):
self.test_bulk()
from frappe.email.bulk import flush
self.test_email_queue()
from frappe.email.queue import flush
flush(from_test=True)
bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Sent'""", as_dict=1)
self.assertEquals(len(bulk), 2)
self.assertTrue('test@example.com' in [d['recipient'] for d in bulk])
self.assertTrue('test1@example.com' in [d['recipient'] for d in bulk])
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])

def test_expired(self):
self.test_bulk()
frappe.db.sql("update `tabBulk Email` set creation='2010-01-01 12:00:00'")
from frappe.email.bulk import flush
self.test_email_queue()
frappe.db.sql("update `tabEmail Queue` set creation='2010-01-01 12:00:00'")
from frappe.email.queue import flush
flush(from_test=True)
bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Expired'""", as_dict=1)
self.assertEquals(len(bulk), 2)
self.assertTrue('test@example.com' in [d['recipient'] for d in bulk])
self.assertTrue('test1@example.com' in [d['recipient'] for d in bulk])
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])

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

self.assertTrue(frappe.db.get_value("Email Unsubscribe",
{"reference_doctype": "User", "reference_name": "Administrator", "email": "test@example.com"}))

before = frappe.db.sql("""select count(name) from `tabBulk Email` 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 Bulk', message='This is a bulk mail!')
subject='Testing Email Queue', message='This is mail is queued!')

# this is sent async (?)

bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Not Sent'""",
email_queue = frappe.db.sql("""select * from `tabEmail Queue` where status='Not Sent'""",
as_dict=1)
self.assertEquals(len(bulk), before + 1)
self.assertFalse('test@example.com' in [d['recipient'] for d in bulk])
self.assertTrue('test1@example.com' in [d['recipient'] for d in bulk])
self.assertTrue('Unsubscribe' in bulk[0]['message'])
def test_bulk_limit(self):
from frappe.email.bulk import send, BulkLimitCrossedError
self.assertRaises(BulkLimitCrossedError, send,
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'])
def test_email_queue_limit(self):
from frappe.email.queue import send, EmailLimitCrossedError
self.assertRaises(EmailLimitCrossedError, send,
recipients=['test@example.com']*1000,
sender="admin@example.com",
reference_doctype = "User", reference_name="Administrator",
subject='Testing Bulk', message='This is a bulk mail!')
subject='Testing Email Queue', message='This email is queued!')

def test_image_parsing(self):
import re


Loading…
Cancel
Save