@@ -309,7 +309,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message | |||
as_markdown=False, bulk=False, 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=(), message_id=None, as_bulk=False, send_after=None): | |||
cc=(), message_id=None, as_bulk=False, send_after=None, expose_recipients=False): | |||
"""Send email using user's default **Email Account** or global default **Email Account**. | |||
@@ -327,6 +327,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message | |||
:param reply_to: Reply-To email id. | |||
: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 send_after: Send after the given datetime. | |||
:param expose_recipients: Display all recipients in the footer message - "This email was sent to" | |||
""" | |||
if bulk or as_bulk: | |||
@@ -335,7 +336,8 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message | |||
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, message_id=message_id, send_after=send_after) | |||
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, send_after=send_after, | |||
expose_recipients=expose_recipients) | |||
else: | |||
import frappe.email | |||
if as_markdown: | |||
@@ -37,20 +37,21 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "sent_or_received", | |||
"depends_on": "", | |||
"fieldname": "communication_medium", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Sent or Received", | |||
"label": "Communication Medium", | |||
"no_copy": 0, | |||
"options": "Sent\nReceived", | |||
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
@@ -59,17 +60,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"fieldname": "recipients", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Status", | |||
"in_list_view": 0, | |||
"label": "Recipients", | |||
"no_copy": 0, | |||
"options": "Open\nReplied\nClosed\nLinked", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -82,16 +81,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"description": "Integrations can use this field to set email delivery status", | |||
"fieldname": "delivery_status", | |||
"fieldtype": "Select", | |||
"hidden": 1, | |||
"depends_on": "eval:doc.communication_medium===\"Email\"", | |||
"fieldname": "cc", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Delivery Status", | |||
"label": "CC", | |||
"no_copy": 0, | |||
"options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
@@ -106,19 +104,20 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "subject", | |||
"depends_on": "eval:doc.communication_medium!==\"Email\"", | |||
"fieldname": "phone_no", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Subject", | |||
"label": "Phone No.", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
@@ -148,15 +147,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "reference_doctype", | |||
"fieldtype": "Link", | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Reference DocType", | |||
"in_list_view": 1, | |||
"label": "Status", | |||
"no_copy": 0, | |||
"options": "DocType", | |||
"options": "Open\nReplied\nClosed\nLinked", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
@@ -171,21 +170,20 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "reference_name", | |||
"fieldtype": "Dynamic Link", | |||
"fieldname": "sent_or_received", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Reference Name", | |||
"in_list_view": 1, | |||
"label": "Sent or Received", | |||
"no_copy": 0, | |||
"options": "reference_doctype", | |||
"options": "Sent\nReceived", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
@@ -194,13 +192,16 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "section_break_8", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"description": "Integrations can use this field to set email delivery status", | |||
"fieldname": "delivery_status", | |||
"fieldtype": "Select", | |||
"hidden": 1, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Delivery Status", | |||
"no_copy": 0, | |||
"options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
@@ -215,37 +216,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Content", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0, | |||
"width": "400" | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "additional_info", | |||
"fieldname": "section_break_10", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Additional Info", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -258,19 +237,19 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "recipients", | |||
"fieldname": "subject", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Recipients", | |||
"label": "Subject", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
@@ -279,15 +258,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "phone_no", | |||
"fieldtype": "Data", | |||
"fieldname": "section_break_8", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Phone No.", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -300,15 +279,14 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "communication_medium", | |||
"fieldtype": "Select", | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Communication Medium", | |||
"in_list_view": 0, | |||
"label": "Content", | |||
"no_copy": 0, | |||
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -316,21 +294,22 @@ | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
"unique": 0, | |||
"width": "400" | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "column_break_14", | |||
"fieldtype": "Column Break", | |||
"collapsible": 1, | |||
"fieldname": "additional_info", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "More Information", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -386,14 +365,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "section_break2", | |||
"fieldtype": "Section Break", | |||
"default": "Today", | |||
"fieldname": "communication_date", | |||
"fieldtype": "Datetime", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Date", | |||
"no_copy": 0, | |||
"options": "simple", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -407,15 +387,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "column_break4", | |||
"fieldname": "column_break_14", | |||
"fieldtype": "Column Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "By", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -428,15 +408,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "email_account", | |||
"fieldname": "reference_doctype", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Email Account", | |||
"label": "Reference DocType", | |||
"no_copy": 0, | |||
"options": "Email Account", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
@@ -451,19 +431,19 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"default": "__user", | |||
"fieldname": "user", | |||
"fieldtype": "Link", | |||
"fieldname": "reference_name", | |||
"fieldtype": "Dynamic Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 1, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "User", | |||
"label": "Reference Name", | |||
"no_copy": 0, | |||
"options": "User", | |||
"options": "reference_doctype", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 1, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -474,17 +454,19 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "column_break5", | |||
"fieldtype": "Column Break", | |||
"fieldname": "in_reply_to", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "On", | |||
"label": "In Reply To", | |||
"no_copy": 0, | |||
"options": "Communication", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -495,16 +477,17 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"default": "Today", | |||
"fieldname": "communication_date", | |||
"fieldtype": "Datetime", | |||
"fieldname": "email_account", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Date", | |||
"label": "Email Account", | |||
"no_copy": 0, | |||
"options": "Email Account", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -517,17 +500,19 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "_user_tags", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"ignore_user_permissions": 0, | |||
"default": "__user", | |||
"fieldname": "user", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 1, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "User Tags", | |||
"no_copy": 1, | |||
"label": "User", | |||
"no_copy": 0, | |||
"options": "User", | |||
"permlevel": 0, | |||
"print_hide": 1, | |||
"read_only": 0, | |||
"print_hide": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -556,6 +541,27 @@ | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "_user_tags", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "User Tags", | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 1, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
@@ -567,7 +573,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-08-14 17:46:20.902296", | |||
"modified": "2015-09-15 05:51:16.112080", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Communication", | |||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals, absolute_import | |||
import frappe | |||
import json | |||
from email.utils import formataddr, parseaddr | |||
from frappe.utils import get_url, get_formatted_email, cstr, cint | |||
from frappe.utils import get_url, get_formatted_email, cstr, cint, validate_email_add, split_emails | |||
from frappe.utils.file_manager import get_file | |||
import frappe.email.smtp | |||
from frappe import _ | |||
@@ -33,6 +33,14 @@ class Communication(Document): | |||
else: | |||
self.status = "Open" | |||
# validate recipients | |||
for email in split_emails(self.recipients): | |||
validate_email_add(email, throw=True) | |||
# validate CC | |||
for email in split_emails(self.cc): | |||
validate_email_add(email, throw=True) | |||
def after_insert(self): | |||
# send new comment to listening clients | |||
comment = self.as_dict() | |||
@@ -73,51 +81,41 @@ class Communication(Document): | |||
self.send_me_a_copy = send_me_a_copy | |||
self.notify(print_html, print_format, attachments, recipients) | |||
def set_incoming_outgoing_accounts(self): | |||
self.incoming_email_account = self.outgoing_email_account = None | |||
if self.reference_doctype: | |||
self.incoming_email_account = frappe.db.get_value("Email Account", | |||
{"append_to": self.reference_doctype, "enable_incoming": 1}, "email_id") | |||
self.outgoing_email_account = frappe.db.get_value("Email Account", | |||
{"append_to": self.reference_doctype, "enable_outgoing": 1}, | |||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) | |||
if not self.incoming_email_account: | |||
self.incoming_email_account = frappe.db.get_value("Email Account", {"default_incoming": 1}, "email_id") | |||
if not self.outgoing_email_account: | |||
self.outgoing_email_account = frappe.db.get_value("Email Account", {"default_outgoing": 1}, | |||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict() | |||
def notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | |||
def notify(self, print_html=None, print_format=None, attachments=None, | |||
recipients=None, cc=None, fetched_from_email_account=False): | |||
"""Calls a delayed celery task 'sendmail' that enqueus email in Bulk Email queue | |||
:param print_html: Send given value as HTML attachment | |||
:param print_format: Attach print format of parent document | |||
:param attachments: A list of filenames that should be attached when sending this email | |||
:param recipients: Email recipients | |||
:param except_recipient: True when pulling email, the notification shouldn't go to the main recipient | |||
:param cc: Send email as CC to | |||
:param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient | |||
""" | |||
recipients, cc = self.get_recipients_and_cc(recipients, cc, | |||
fetched_from_email_account=fetched_from_email_account) | |||
self.emails_not_sent_to = set(self.all_email_addresses) - set(recipients) - set(cc) | |||
if frappe.flags.in_test: | |||
# for test cases, run synchronously | |||
self._notify(print_html=print_html, print_format=print_format, attachments=attachments, | |||
recipients=recipients, except_recipient=except_recipient) | |||
recipients=recipients, cc=cc) | |||
else: | |||
from frappe.tasks import sendmail | |||
sendmail.delay(frappe.local.site, self.name, | |||
print_html=print_html, print_format=print_format, attachments=attachments, | |||
recipients=recipients, except_recipient=except_recipient) | |||
recipients=recipients, cc=cc) | |||
def _notify(self, print_html=None, print_format=None, attachments=None, | |||
recipients=None, cc=None): | |||
def _notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | |||
self.prepare_to_notify(print_html, print_format, attachments) | |||
if not recipients: | |||
recipients = self.get_recipients(except_recipient=except_recipient) | |||
frappe.sendmail( | |||
recipients=recipients, | |||
recipients=(recipients or []) + (cc or []), | |||
expose_recipients=True, | |||
sender=self.sender, | |||
reply_to=self.incoming_email_account, | |||
subject=self.subject, | |||
@@ -130,6 +128,27 @@ class Communication(Document): | |||
bulk=True | |||
) | |||
def get_recipients_and_cc(self, recipients, cc, fetched_from_email_account=False): | |||
self.all_email_addresses = [] | |||
if not recipients: | |||
recipients = self.get_recipients() | |||
if not cc: | |||
cc = self.get_cc(recipients, fetched_from_email_account=fetched_from_email_account) | |||
if fetched_from_email_account: | |||
# email was already sent to the original recipient by the sender's email service | |||
original_recipients, recipients = recipients, [] | |||
# cc that was received in the email | |||
original_cc = split_emails(self.cc) | |||
# don't cc to people who already received the mail from sender's email service | |||
cc = list(set(cc) - set(original_cc) - set(original_recipients)) | |||
return recipients, cc | |||
def prepare_to_notify(self, print_html=None, print_format=None, attachments=None): | |||
"""Prepare to make multipart MIME Email | |||
@@ -165,78 +184,129 @@ class Communication(Document): | |||
else: | |||
self.attachments.append(a) | |||
def get_recipients(self, except_recipient=False): | |||
"""Build a list of users to which this email should go to""" | |||
def set_incoming_outgoing_accounts(self): | |||
self.incoming_email_account = self.outgoing_email_account = None | |||
if self.reference_doctype: | |||
self.incoming_email_account = frappe.db.get_value("Email Account", | |||
{"append_to": self.reference_doctype, "enable_incoming": 1}, "email_id") | |||
self.outgoing_email_account = frappe.db.get_value("Email Account", | |||
{"append_to": self.reference_doctype, "enable_outgoing": 1}, | |||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) | |||
if not self.incoming_email_account: | |||
self.incoming_email_account = frappe.db.get_value("Email Account", {"default_incoming": 1}, "email_id") | |||
if not self.outgoing_email_account: | |||
self.outgoing_email_account = frappe.db.get_value("Email Account", {"default_outgoing": 1}, | |||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict() | |||
def get_recipients(self): | |||
"""Build a list of email addresses for To""" | |||
# [EDGE CASE] self.recipients can be None when an email is sent as BCC | |||
original_recipients = [s.strip() for s in cstr(self.recipients).split(",")] | |||
recipients = original_recipients[:] | |||
recipients = split_emails(self.recipients) | |||
if recipients: | |||
# this will be used to eventually find email addresses that aren't sent to | |||
self.all_email_addresses.extend(recipients) | |||
# exclude email accounts | |||
exclude = [d[0] for d in | |||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)] | |||
exclude += [d[0] for d in | |||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True) | |||
if d[0]] | |||
recipients = self.filter_email_list(recipients, exclude) | |||
return recipients | |||
def get_cc(self, recipients=None, fetched_from_email_account=False): | |||
"""Build a list of email addresses for CC""" | |||
# get a copy of CC list | |||
cc = split_emails(self.cc) | |||
if self.reference_doctype and self.reference_name: | |||
recipients += self.get_earlier_participants() | |||
recipients += self.get_commentors() | |||
recipients += self.get_assignees() | |||
recipients += self.get_starrers() | |||
if not cc or fetched_from_email_account: | |||
# if CC is not mentioned from the UI or is a fetched email, add follows to CC | |||
cc.append(self.get_owner_email()) | |||
cc += self.get_assignees() | |||
cc += self.get_starrers() | |||
if fetched_from_email_account and self.in_reply_to: | |||
# add sender of previous reply | |||
cc.append(frappe.db.get_value("Communication", self.in_reply_to, "sender")) | |||
if cc: | |||
# this will be used to eventually find email addresses that aren't sent to | |||
self.all_email_addresses.extend(cc) | |||
# exclude email accounts, unfollows, recipients and unsubscribes | |||
exclude = [d[0] for d in | |||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)] | |||
exclude += [d[0] for d in | |||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True) | |||
if d[0]] | |||
exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)] | |||
exclude += [parseaddr(email)[1] for email in recipients] | |||
if fetched_from_email_account: | |||
# exclude sender when pulling email | |||
exclude += [parseaddr(self.sender)[1]] | |||
# remove unsubscribed recipients | |||
unsubscribed = [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)] | |||
email_accounts = [d[0] for d in frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)] | |||
sender = parseaddr(self.sender)[1] | |||
if self.reference_doctype and self.reference_name: | |||
exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"], | |||
{"reference_doctype": self.reference_doctype, "reference_name": self.reference_name}, as_list=True)] | |||
filtered = [] | |||
email_addresses = [] | |||
for e in list(set(recipients)): | |||
if (e=="Administrator") or ((e==self.sender) and (e not in original_recipients)) or \ | |||
(e in unsubscribed) or (e in email_accounts): | |||
continue | |||
cc = self.filter_email_list(cc, exclude) | |||
if getattr(self, "send_me_a_copy", False) and self.sender not in cc: | |||
self.all_email_addresses.append(self.sender) | |||
cc.append(self.sender) | |||
email_id = parseaddr(e)[1] | |||
return cc | |||
def filter_email_list(self, email_list, exclude): | |||
# temp variables | |||
filtered = [] | |||
email_address_list = [] | |||
if not email_id: | |||
for email in list(set(email_list)): | |||
if email in exclude: | |||
continue | |||
if email_id==sender or email_id in unsubscribed or email_id in email_accounts: | |||
email_address = (parseaddr(email)[1] or "").lower() | |||
if not email_address: | |||
continue | |||
if except_recipient and (e==self.recipients or email_id==self.recipients): | |||
# while pulling email, don't send email to current recipient | |||
if email_address in exclude: | |||
continue | |||
# make sure of case-insensitive uniqueness of email address | |||
if email_id.lower() not in email_addresses: | |||
if email_address not in email_address_list: | |||
# append the full email i.e. "Human <human@example.com>" | |||
filtered.append(e) | |||
email_addresses.append(email_id.lower()) | |||
if getattr(self, "send_me_a_copy", False): | |||
filtered.append(self.sender) | |||
filtered.append(email) | |||
email_address_list.append(email_address) | |||
return filtered | |||
def get_starrers(self): | |||
"""Return list of users who have starred this document.""" | |||
if self.reference_doctype and self.reference_name: | |||
return self.get_parent_doc().get_starred_by() | |||
else: | |||
return [] | |||
def get_earlier_participants(self): | |||
return frappe.db.sql_list(""" | |||
select distinct sender | |||
from tabCommunication where | |||
reference_doctype=%s and reference_name=%s""", | |||
(self.reference_doctype, self.reference_name)) | |||
def get_commentors(self): | |||
return frappe.db.sql_list(""" | |||
select distinct comment_by | |||
from tabComment where | |||
comment_doctype=%s and comment_docname=%s and | |||
ifnull(unsubscribed, 0)=0 and comment_by!='Administrator'""", | |||
(self.reference_doctype, self.reference_name)) | |||
return [( get_formatted_email(user) or user ) for user in self.get_parent_doc().get_starred_by()] | |||
def get_owner_email(self): | |||
owner = self.get_parent_doc().owner | |||
return get_formatted_email(owner) or owner | |||
def get_assignees(self): | |||
return [d.owner for d in frappe.db.get_all("ToDo", filters={"reference_type": self.reference_doctype, | |||
"reference_name": self.reference_name, "status": "Open"}, fields=["owner"])] | |||
return [( get_formatted_email(d.owner) or d.owner ) for d in | |||
frappe.db.get_all("ToDo", filters={ | |||
"reference_type": self.reference_doctype, | |||
"reference_name": self.reference_name, | |||
"status": "Open" | |||
}, fields=["owner"]) | |||
] | |||
def get_attach_link(self, print_format): | |||
"""Returns public link for the attachment via `templates/emails/print_link.html`.""" | |||
@@ -256,7 +326,7 @@ def on_doctype_update(): | |||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", | |||
sender=None, recipients=None, communication_medium="Email", send_email=False, | |||
print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False, | |||
send_me_a_copy=False): | |||
send_me_a_copy=False, cc=None): | |||
"""Make a new communication. | |||
:param doctype: Reference DocType. | |||
@@ -289,6 +359,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = | |||
"content": content, | |||
"sender": sender, | |||
"recipients": recipients, | |||
"cc": cc or None, | |||
"communication_medium": "Email", | |||
"sent_or_received": sent_or_received, | |||
"reference_doctype": doctype, | |||
@@ -300,15 +371,13 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = | |||
# if not committed, delayed task doesn't find the communication | |||
frappe.db.commit() | |||
recipients = None | |||
if send_email: | |||
comm.send_me_a_copy = send_me_a_copy | |||
recipients = comm.get_recipients() | |||
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy, recipients=recipients) | |||
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy) | |||
return { | |||
"name": comm.name, | |||
"recipients": ", ".join(recipients) if recipients else None | |||
"emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None | |||
} | |||
@frappe.whitelist() | |||
@@ -10,13 +10,14 @@ from frappe.email.smtp import SMTPServer, get_outgoing_email_account | |||
from frappe.email.email_body import get_email, get_formatted_html | |||
from frappe.utils.verified_command import get_signed_params, verify_request | |||
from html2text import html2text | |||
from frappe.utils import get_url, nowdate, encode, now_datetime, add_days | |||
from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split_emails | |||
class BulkLimitCrossedError(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=(), message_id=None, send_after=None): | |||
attachments=None, reply_to=None, cc=(), message_id=None, send_after=None, | |||
expose_recipients=False): | |||
"""Add email to sending queue (Bulk Email) | |||
:param recipients: List of recipients. | |||
@@ -39,7 +40,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||
return | |||
if isinstance(recipients, basestring): | |||
recipients = recipients.split(",") | |||
recipients = split_emails(recipients) | |||
if isinstance(send_after, int): | |||
send_after = add_days(nowdate(), send_after) | |||
@@ -66,23 +67,30 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||
else: | |||
unsubscribed = [] | |||
for email in filter(None, list(set(recipients))): | |||
if email not in unsubscribed: | |||
email_content = formatted | |||
email_text_context = text_content | |||
recipients = [r for r in list(set(recipients)) if r and r not in unsubscribed] | |||
if reference_doctype: | |||
unsubscribe_url = get_unsubcribed_url(reference_doctype, reference_name, email, | |||
unsubscribe_method, unsubscribe_params) | |||
for email in recipients: | |||
email_content = formatted | |||
email_text_context = text_content | |||
# add to queue | |||
email_content = add_unsubscribe_link(email_content, email, reference_doctype, | |||
reference_name, unsubscribe_url, unsubscribe_message) | |||
if reference_doctype: | |||
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 | |||
) | |||
email_text_context += "\n" + _("This email was sent to {0}. To unsubscribe click on this link: {1}").format(email, unsubscribe_url) | |||
email_content = email_content.replace("<!--unsubscribe link here-->", unsubscribe_link.html) | |||
email_text_context += unsubscribe_link.text | |||
add(email, sender, subject, email_content, email_text_context, reference_doctype, | |||
reference_name, attachments, reply_to, cc, message_id, send_after) | |||
# add to queue | |||
add(email, sender, subject, email_content, email_text_context, reference_doctype, | |||
reference_name, attachments, reply_to, cc, message_id, send_after) | |||
def add(email, sender, subject, formatted, text_content=None, | |||
reference_doctype=None, reference_name=None, attachments=None, reply_to=None, | |||
@@ -129,18 +137,41 @@ def check_bulk_limit(recipients): | |||
throw(_("Email limit {0} crossed").format(monthly_bulk_mail_limit), | |||
BulkLimitCrossedError) | |||
def add_unsubscribe_link(message, email, reference_doctype, reference_name, unsubscribe_url, unsubscribe_message): | |||
unsubscribe_link = """<div style="padding: 7px; text-align: center; color: #8D99A6;"> | |||
{email}. <a href="{unsubscribe_url}" style="color: #8D99A6; text-decoration: underline; | |||
target="_blank">{unsubscribe_message}. | |||
</a> | |||
</div>""".format(unsubscribe_url = unsubscribe_url, | |||
email= _("This email was sent to {0}").format(email), | |||
unsubscribe_message = unsubscribe_message or _("Unsubscribe from this list")) | |||
message = message.replace("<!--unsubscribe link here-->", unsubscribe_link) | |||
return message | |||
def get_unsubscribe_link(reference_doctype, reference_name, | |||
email, recipients, expose_recipients, unsubscribe_method, unsubscribe_params, unsubscribe_message): | |||
unsubscribe_email = recipients if expose_recipients else [email] | |||
unsubscribe_email = _("This email was sent to {0}").format(", ".join(unsubscribe_email)) | |||
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} | |||
<p style="margin: 15px auto;"> | |||
<a href="{unsubscribe_url}" style="color: #8d99a6; text-decoration: underline; | |||
target="_blank">{unsubscribe_message} | |||
</a> | |||
</p> | |||
</div>""".format( | |||
unsubscribe_url = unsubscribe_url, | |||
email=unsubscribe_email, | |||
unsubscribe_message=unsubscribe_message | |||
) | |||
text = "\n{email}\n\n{unsubscribe_message}: {unsubscribe_url}".format( | |||
email=unsubscribe_email, | |||
unsubscribe_message=unsubscribe_message, | |||
unsubscribe_url=unsubscribe_url | |||
) | |||
return frappe._dict({ | |||
"html": html, | |||
"text": text | |||
}) | |||
def get_unsubcribed_url(reference_doctype, reference_name, email, unsubscribe_method, unsubscribe_params): | |||
params = {"email": email.encode("utf-8"), | |||
@@ -137,7 +137,7 @@ class EmailAccount(Document): | |||
else: | |||
frappe.db.commit() | |||
communication.notify(attachments=communication._attachments, except_recipient=True) | |||
communication.notify(attachments=communication._attachments, fetched_from_email_account=True) | |||
if exceptions: | |||
raise Exception, frappe.as_json(exceptions) | |||
@@ -158,6 +158,7 @@ class EmailAccount(Document): | |||
"sender_full_name": email.from_real_name, | |||
"sender": email.from_email, | |||
"recipients": email.mail.get("To"), | |||
"cc": email.mail.get("CC"), | |||
"email_account": self.name, | |||
"communication_medium": "Email" | |||
}) | |||
@@ -208,6 +209,9 @@ class EmailAccount(Document): | |||
if frappe.db.exists("Communication", in_reply_to): | |||
parent = frappe.get_doc("Communication", in_reply_to) | |||
# set in_reply_to of current communication | |||
communication.in_reply_to = in_reply_to | |||
if parent.reference_name: | |||
parent = frappe.get_doc(parent.reference_doctype, | |||
parent.reference_name) | |||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals | |||
import frappe | |||
from frappe.utils.pdf import get_pdf | |||
from frappe.email.smtp import get_outgoing_email_account | |||
from frappe.utils import get_url, scrub_urls, strip, expand_relative_urls, cint | |||
from frappe.utils import get_url, scrub_urls, strip, expand_relative_urls, cint, split_emails | |||
import email.utils | |||
from markdown2 import markdown | |||
@@ -42,7 +42,7 @@ class EMail: | |||
if isinstance(recipients, basestring): | |||
recipients = recipients.replace(';', ',').replace('\n', '') | |||
recipients = recipients.split(',') | |||
recipients = split_emails(recipients) | |||
# remove null | |||
recipients = filter(None, (strip(r) for r in recipients)) | |||
@@ -238,18 +238,18 @@ def get_footer(email_account, footer=None): | |||
footer = footer or "" | |||
if email_account and email_account.footer: | |||
footer += email_account.footer | |||
footer += '<div style="margin: 15px auto;">{0}</div>'.format(email_account.footer) | |||
footer += "<!--unsubscribe link here-->" | |||
company_address = frappe.db.get_default("email_footer_address") | |||
if company_address: | |||
footer += '<div style="text-align: center; color: #8d99a6">{0}</div>'\ | |||
footer += '<div style="margin: 15px auto; text-align: center; color: #8d99a6">{0}</div>'\ | |||
.format(company_address.replace("\n", "<br>")) | |||
if not cint(frappe.db.get_default("disable_standard_email_footer")): | |||
for default_mail_footer in frappe.get_hooks("default_mail_footer"): | |||
footer += default_mail_footer | |||
footer += '<div style="margin: 15px auto;">{0}</div>'.format(default_mail_footer) | |||
return footer |
@@ -193,6 +193,29 @@ $.extend(frappe.user, { | |||
is_report_manager: function() { | |||
return frappe.user.has_role(['Administrator', 'System Manager', 'Report Manager']); | |||
}, | |||
get_formatted_email: function(email) { | |||
var fullname = frappe.user.full_name(email); | |||
if (!fullname) { | |||
return email; | |||
} else { | |||
// to quote or to not | |||
var quote = ''; | |||
// only if these special characters are found | |||
// why? To make the output same as that in python! | |||
if (fullname.search(/[\[\]\\()<>@,:;".]/) !== -1) { | |||
quote = '"'; | |||
} | |||
return repl('%(quote)s%(fullname)s%(quote)s <%(email)s>', { | |||
fullname: fullname, | |||
email: email, | |||
quote: quote | |||
}); | |||
} | |||
} | |||
}); | |||
frappe.session_alive = true; | |||
@@ -43,6 +43,9 @@ frappe.utils = { | |||
}); | |||
return out.join(newline); | |||
}, | |||
escape_html: function(txt) { | |||
return $("<div></div>").text(txt || "").html(); | |||
}, | |||
is_url: function(txt) { | |||
return txt.toLowerCase().substr(0,7)=='http://' | |||
|| txt.toLowerCase().substr(0,8)=='https://' | |||
@@ -2,12 +2,17 @@ | |||
// MIT License. See license.txt | |||
frappe.ui.is_starred = function(doc) { | |||
var starred = frappe.ui.get_starred_by(doc); | |||
return starred.indexOf(user)===-1 ? false : true; | |||
} | |||
frappe.ui.get_starred_by = function(doc) { | |||
var starred = doc._starred_by; | |||
if(starred) { | |||
starred = JSON.parse(starred); | |||
return starred.indexOf(user)===-1 ? false : true; | |||
} | |||
return false; | |||
return starred || []; | |||
} | |||
frappe.ui.toggle_star = function($btn, doctype, name) { | |||
@@ -14,41 +14,7 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
this.dialog = new frappe.ui.Dialog({ | |||
title: __("Add Reply") + ": " + (this.subject || ""), | |||
no_submit_on_enter: true, | |||
fields: [ | |||
{label:__("To"), fieldtype:"Data", reqd: 1, fieldname:"recipients"}, | |||
{fieldtype: "Section Break"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Subject"), fieldtype:"Data", reqd: 1, | |||
fieldname:"subject"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply", | |||
fieldname:"standard_reply"}, | |||
{fieldtype: "Section Break"}, | |||
{label:__("Message"), fieldtype:"Text Editor", reqd: 1, | |||
fieldname:"content"}, | |||
{fieldtype: "Section Break"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Send As Email"), fieldtype:"Check", | |||
fieldname:"send_email"}, | |||
{label:__("Send me a copy"), fieldtype:"Check", | |||
fieldname:"send_me_a_copy"}, | |||
{label:__("Communication Medium"), fieldtype:"Select", | |||
options: ["Phone", "Chat", "Email", "SMS", "Visit", "Other"], | |||
fieldname:"communication_medium"}, | |||
{label:__("Sent or Received"), fieldtype:"Select", | |||
options: ["Received", "Sent"], | |||
fieldname:"sent_or_received"}, | |||
{label:__("Attach Document Print"), fieldtype:"Check", | |||
fieldname:"attach_document_print"}, | |||
{label:__("Select Print Format"), fieldtype:"Select", | |||
fieldname:"select_print_format"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Select Attachments"), fieldtype:"HTML", | |||
fieldname:"select_attachments"} | |||
], | |||
fields: this.get_fields(), | |||
primary_action_label: "Send", | |||
primary_action: function() { | |||
me.send_action(); | |||
@@ -79,6 +45,100 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
this.dialog.show(); | |||
}, | |||
get_fields: function() { | |||
var cc_fields = this.get_cc_fields(); | |||
var fields_before_cc = [ | |||
{fieldtype: "Section Break"}, | |||
{label:__("To"), fieldtype:"Data", reqd: 1, fieldname:"recipients"}, | |||
{fieldtype: "Section Break", collapsible: 1, label: "CC & Standard Reply"}, | |||
{label:__("CC"), fieldtype:"Data", fieldname:"cc"}, | |||
]; | |||
var fields_after_cc = [ | |||
{label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply", | |||
fieldname:"standard_reply"}, | |||
{fieldtype: "Section Break"}, | |||
{label:__("Subject"), fieldtype:"Data", reqd: 1, | |||
fieldname:"subject"}, | |||
{fieldtype: "Section Break"}, | |||
{label:__("Message"), fieldtype:"Text Editor", reqd: 1, | |||
fieldname:"content"}, | |||
{fieldtype: "Section Break"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Send As Email"), fieldtype:"Check", | |||
fieldname:"send_email"}, | |||
{label:__("Send me a copy"), fieldtype:"Check", | |||
fieldname:"send_me_a_copy"}, | |||
{label:__("Communication Medium"), fieldtype:"Select", | |||
options: ["Phone", "Chat", "Email", "SMS", "Visit", "Other"], | |||
fieldname:"communication_medium"}, | |||
{label:__("Sent or Received"), fieldtype:"Select", | |||
options: ["Received", "Sent"], | |||
fieldname:"sent_or_received"}, | |||
{label:__("Attach Document Print"), fieldtype:"Check", | |||
fieldname:"attach_document_print"}, | |||
{label:__("Select Print Format"), fieldtype:"Select", | |||
fieldname:"select_print_format"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Select Attachments"), fieldtype:"HTML", | |||
fieldname:"select_attachments"} | |||
]; | |||
return fields_before_cc.concat(cc_fields).concat(fields_after_cc); | |||
}, | |||
get_cc_fields: function() { | |||
var cc = [ [this.frm.doc.owner, 1] ]; | |||
var starred_by = frappe.ui.get_starred_by(this.frm.doc); | |||
if (starred_by) { | |||
for ( var i=0, l=starred_by.length; i<l; i++ ) { | |||
cc.push( [starred_by[i], 1] ); | |||
} | |||
} | |||
var assignments = this.frm.get_docinfo().assignments; | |||
if (assignments) { | |||
for ( var i=0, l=assignments.length; i<l; i++ ) { | |||
cc.push( [assignments[i].owner, 1] ); | |||
} | |||
} | |||
var comments = this.frm.get_docinfo().comments; | |||
if (comments) { | |||
for ( var i=0, l=comments.length; i<l; i++ ) { | |||
cc.push( [comments[i].comment_by, 0] ); | |||
} | |||
} | |||
var added = []; | |||
var cc_fields = []; | |||
for ( var i=0, l=cc.length; i<l; i++ ) { | |||
var email = cc[i][0]; | |||
var default_value = cc[i][1]; | |||
if ( !email || added.indexOf(email)!==-1 || email.indexOf("@")===-1 ) { | |||
continue; | |||
} | |||
// for deduplication | |||
added.push(email); | |||
email = frappe.user.get_formatted_email(email); | |||
cc_fields.push({ | |||
"label": frappe.utils.escape_html(email), | |||
"fieldtype": "Check", | |||
"fieldname": email, | |||
"is_cc_checkbox": 1, | |||
"default": default_value | |||
}); | |||
} | |||
return cc_fields; | |||
}, | |||
prepare: function() { | |||
this.setup_subject_and_recipients(); | |||
this.setup_print(); | |||
@@ -284,10 +344,10 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
}, | |||
send_action: function() { | |||
var me = this, | |||
form_values = me.dialog.get_values(), | |||
btn = me.dialog.get_primary_btn(); | |||
var me = this; | |||
var btn = me.dialog.get_primary_btn(); | |||
var form_values = this.get_values(); | |||
if(!form_values) return; | |||
var selected_attachments = $.map($(me.dialog.wrapper) | |||
@@ -312,6 +372,26 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
} | |||
}, | |||
get_values: function() { | |||
var form_values = this.dialog.get_values(); | |||
// cc | |||
for ( var i=0, l=this.dialog.fields.length; i < l; i++ ) { | |||
var df = this.dialog.fields[i]; | |||
if ( df.is_cc_checkbox ) { | |||
// concat in cc | |||
if ( form_values[df.fieldname] ) { | |||
form_values.cc = ( form_values.cc ? (form_values.cc + ", ") : "" ) + df.fieldname; | |||
} | |||
delete form_values[df.fieldname]; | |||
} | |||
} | |||
return form_values; | |||
}, | |||
send_email: function(btn, form_values, selected_attachments, print_html, print_format) { | |||
var me = this; | |||
@@ -334,6 +414,7 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
method:"frappe.core.doctype.communication.communication.make", | |||
args: { | |||
recipients: form_values.recipients, | |||
cc: form_values.cc, | |||
subject: form_values.subject, | |||
content: form_values.content, | |||
doctype: me.doc.doctype, | |||
@@ -349,8 +430,11 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
btn: btn, | |||
callback: function(r) { | |||
if(!r.exc) { | |||
if(form_values.send_email && r.message["recipients"]) | |||
msgprint(__("Email sent to {0}", [r.message["recipients"]])); | |||
if(form_values.send_email && r.message["emails_not_sent_to"]) { | |||
msgprint( __("Email not sent to {0}", | |||
[ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) ); | |||
} | |||
me.dialog.hide(); | |||
if (cur_frm) { | |||
@@ -185,7 +185,8 @@ def run_async_task(self, site=None, user=None, cmd=None, form_dict=None, hijack_ | |||
@celery_task() | |||
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | |||
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None, | |||
recipients=None, cc=None): | |||
try: | |||
frappe.connect(site=site) | |||
@@ -193,7 +194,8 @@ def sendmail(site, communication_name, print_html=None, print_format=None, attac | |||
for i in xrange(3): | |||
try: | |||
communication = frappe.get_doc("Communication", communication_name) | |||
communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, recipients=recipients, except_recipient=except_recipient) | |||
communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, | |||
recipients=recipients, cc=cc) | |||
except MySQLdb.OperationalError, e: | |||
# deadlock, try again | |||
if e.args[0]==1213: | |||
@@ -9,7 +9,6 @@ import os, sys, re, urllib | |||
import frappe | |||
import requests | |||
# utility functions like cint, int, flt, etc. | |||
from frappe.utils.data import * | |||
@@ -89,6 +88,15 @@ def validate_email_add(email_str, throw=False): | |||
return matched | |||
def split_emails(txt): | |||
email_list = [] | |||
for email in re.split(''',(?=(?:[^"]|"[^"]*")*$)''', cstr(txt)): | |||
email = strip(cstr(email)) | |||
if email: | |||
email_list.append(email) | |||
return email_list | |||
def random_string(length): | |||
"""generate a random string""" | |||
import string | |||