diff --git a/frappe/__init__.py b/frappe/__init__.py index 781f850a86..d526a24bcc 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -225,22 +225,22 @@ def get_request_header(key, default=None): def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", as_markdown=False, bulk=False, ref_doctype=None, ref_docname=None, - add_unsubscribe_link=False, attachments=None): + add_unsubscribe_link=False, attachments=None, content=None): if bulk: import frappe.email.bulk frappe.email.bulk.send(recipients=recipients, sender=sender, - subject=subject, message=message, ref_doctype = ref_doctype, + subject=subject, message=content or message, ref_doctype = ref_doctype, ref_docname = ref_docname, add_unsubscribe_link=add_unsubscribe_link, attachments=attachments) else: import frappe.email if as_markdown: frappe.email.sendmail_md(recipients, sender=sender, - subject=subject, msg=message, attachments=attachments) + subject=subject, msg=content or message, attachments=attachments) else: frappe.email.sendmail(recipients, sender=sender, - subject=subject, msg=message, attachments=attachments) + subject=subject, msg=content or message, attachments=attachments) logger = None whitelisted = [] diff --git a/frappe/cli.py b/frappe/cli.py index 9c3f1055d4..0fa3c515d9 100755 --- a/frappe/cli.py +++ b/frappe/cli.py @@ -801,7 +801,7 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driv import frappe.test_runner from frappe.utils import sel - sel.start(verbose, driver) + #sel.start(verbose, driver) ret = 1 try: diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 7cf5a48edd..8bdce2c506 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -5,31 +5,66 @@ from __future__ import unicode_literals import frappe import json import urllib -from email.utils import formataddr from frappe.website.utils import is_signup_enabled from frappe.utils import get_url, cstr, cint, scrub_urls from frappe.email.email_body import get_email -from frappe.email.smtp import send +import frappe.email.smtp from frappe import _ from frappe.model.document import Document class Communication(Document): def validate(self): - if not self.parentfield: - self.parentfield = "communications" + if not self.sender: + self.sender = frappe.db.get_value("User", frappe.session.user, "email") def get_parent_doc(self): - return frappe.get_doc(self.parenttype, self.parent) - - def update_parent(self): - """update status of parent Lead or Contact based on who is replying""" - parent_doc = self.get_parent_doc() - parent_doc.run_method("on_communication") + if not hasattr(self, "parent_doc"): + if self.reference_doctype and self.reference_name: + self.parent_doc = frappe.get_doc(self.reference_doctype, self.reference_name) + else: + self.parent_doc = None + return self.parent_doc def on_update(self): self.update_parent() + def update_parent(self): + """update status of parent Lead or Contact based on who is replying""" + parent = self.get_parent_doc() + if not parent: + return + + status_field = parent.meta.get_field("status") + + if status_field and "Open" in (status_field.options or "").split("\n"): + frappe.db.set_value(parent.doctype, parent.name, "status", + "Open" if self.sent_or_received=="Received" else "Replied") + + def send(self, send_me_a_copy=False, print_html=None, print_format=None, + attachments=None): + mail = get_email(self.recipients, sender=self.sender, subject=self.subject, + content=self.content) + + mail.set_message_id(self.name) + + if send_me_a_copy: + mail.cc.append(frappe.db.get_value("User", frappe.session.user, "email")) + + if print_html or print_format: + attach_print(mail, self.get_parent_doc(), print_html, print_format) + + if isinstance(attachments, basestring): + attachments = json.loads(attachments) + + for a in attachments: + try: + mail.attach_file(a) + except IOError: + frappe.throw(_("Unable to find attachment {0}").format(a)) + + frappe.email.smtp.send(mail) + def on_doctype_update(): frappe.db.add_index("Communication", ["reference_doctype", "reference_name"]) @@ -42,112 +77,24 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format( doctype=doctype, name=name)) - _make(doctype=doctype, name=name, content=content, subject=subject, sent_or_received=sent_or_received, - sender=sender, recipients=recipients, communication_medium=communication_medium, send_email=send_email, - print_html=print_html, print_format=print_format, attachments=attachments, send_me_a_copy=send_me_a_copy, set_lead=set_lead, - date=date) - -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='[]', send_me_a_copy=False, set_lead=True, date=None): - - # add to Communication - sent_via = None - - # since we are using fullname and email, - # if the fullname has any incompatible characters,formataddr can deal with it - try: - sender = json.loads(sender) - except ValueError: - pass - - if isinstance(sender, (tuple, list)) and len(sender)==2: - sender = formataddr(sender) - - comm = frappe.new_doc('Communication') - d = comm - d.subject = subject - d.content = content - d.sent_or_received = sent_or_received - d.sender = sender or frappe.db.get_value("User", frappe.session.user, "email") - d.recipients = recipients - - # add as child - sent_via = frappe.get_doc(doctype, name) - d.parent = name - d.parenttype = doctype - d.parentfield = "communications" - - if date: - d.communication_date = date - - d.communication_medium = communication_medium - - d.idx = cint(frappe.db.sql("""select max(idx) from `tabCommunication` - where parenttype=%s and parent=%s""", (doctype, name))[0][0]) + 1 - - comm.ignore_permissions = True - comm.insert() + comm = frappe.get_doc({ + "doctype":"Communication", + "subject": subject, + "content": content, + "sender": sender, + "recipients": recipients, + "communication_medium": "Email", + "sent_or_received": sent_or_received, + }) + comm.insert(ignore_permissions=True) if send_email: - d = comm - send_comm_email(d, name, sent_via, print_html, print_format, attachments, send_me_a_copy) + comm.send(send_me_a_copy, print_html, print_format, attachments) -@frappe.whitelist() -def get_customer_supplier(args=None): - """ - Get Customer/Supplier, given a contact, if a unique match exists - """ - if not args: args = frappe.local.form_dict - if not args.get('contact'): - raise Exception, "Please specify a contact to fetch Customer/Supplier" - result = frappe.db.sql("""\ - select customer, supplier - from `tabContact` - where name = %s""", args.get('contact'), as_dict=1) - if result and len(result)==1 and (result[0]['customer'] or result[0]['supplier']): - return { - 'fieldname': result[0]['customer'] and 'customer' or 'supplier', - 'value': result[0]['customer'] or result[0]['supplier'] - } - return {} - -def send_comm_email(d, name, sent_via=None, print_html=None, print_format=None, attachments='[]', send_me_a_copy=False): - footer = None - - if sent_via: - if hasattr(sent_via, "get_sender"): - d.sender = sent_via.get_sender(d) or d.sender - if hasattr(sent_via, "get_subject"): - d.subject = sent_via.get_subject(d) - if hasattr(sent_via, "get_content"): - d.content = sent_via.get_content(d) - - footer = "
" + set_portal_link(sent_via, d) - - mail = get_email(d.recipients, sender=d.sender, subject=d.subject, - msg=d.content, footer=footer) - - mail.set_message_id(d.name) - - if send_me_a_copy: - mail.cc.append(frappe.db.get_value("User", frappe.session.user, "email")) - - if print_html or print_format: - attach_print(mail, sent_via, print_html, print_format) - - for a in json.loads(attachments): - try: - mail.attach_file(a) - except IOError: - frappe.throw(_("Unable to find attachment {0}").format(a)) - - send(mail) - -def attach_print(mail, sent_via, print_html, print_format): - name = sent_via.name - if not print_html and print_format: - print_html = frappe.get_print_format(sent_via.doctype, sent_via.name, print_format) +def attach_print(mail, parent_doc, print_html, print_format): + name = parent_doc.name if parent_doc else "attachment" + if (not print_html) and parent_doc and print_format: + print_html = frappe.get_print_format(parent_doc.doctype, parent_doc.name, print_format) print_settings = frappe.db.get_singles_dict("Print Settings") send_print_as_pdf = cint(print_settings.send_print_as_pdf) diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index c77b420eeb..08c892684e 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -7,14 +7,14 @@ import frappe from frappe.email.email_body import get_email from frappe.email.smtp import send -def sendmail_md(recipients, sender=None, msg=None, subject=None, attachments=None): +def sendmail_md(recipients, sender=None, msg=None, subject=None, attachments=None, content=None): """send markdown email""" import markdown2 - sendmail(recipients, sender, markdown2.markdown(msg), subject, attachments) + sendmail(recipients, sender, markdown2.markdown(content or msg), subject, attachments) -def sendmail(recipients, sender='', msg='', subject='[No Subject]', attachments=None): +def sendmail(recipients, sender='', msg='', subject='[No Subject]', attachments=None, content=None): """send an html email as multipart with attachments and all""" - send(get_email(recipients, sender, msg, subject, attachments=attachments)) + send(get_email(recipients, sender, content or msg, subject, attachments=attachments)) def sendmail_to_system_managers(subject, content): send(get_email(get_system_managers(), None, content, subject)) @@ -34,7 +34,6 @@ def get_contact_list(): def get_system_managers(): return frappe.db.sql_list("""select parent FROM tabUserRole - WHERE role='System Manager' - AND parent!='Administrator' - AND parent IN - (SELECT email FROM tabUser WHERE enabled=1)""") + WHERE role='System Manager' + AND parent!='Administrator' + AND parent IN (SELECT email FROM tabUser WHERE enabled=1)""") diff --git a/frappe/email/bulk.py b/frappe/email/bulk.py index 1b5b970ea9..4f2727102f 100644 --- a/frappe/email/bulk.py +++ b/frappe/email/bulk.py @@ -6,7 +6,7 @@ import frappe import HTMLParser import urllib from frappe import msgprint, throw, _ -from frappe.email.smtp import SMTPServer +from frappe.email.smtp import SMTPServer, get_outgoing_email_account from frappe.email.email_body import get_email, get_formatted_html from frappe.email.html2text import html2text from frappe.utils import cint, get_url, nowdate @@ -57,7 +57,7 @@ def send(recipients=None, sender=None, doctype='User', email_field='email', if not recipients: recipients = [] if not sender or sender == "Administrator": - sender = frappe.db.get_value('Outgoing Email Settings', None, 'auto_email_id') + sender = get_outgoing_email_account().email_id check_bulk_limit(len(recipients)) formatted = get_formatted_html(subject, message) diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index 9c81435c9f..65c81023e9 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -26,7 +26,7 @@ }, { "description": "Check this if this is a global email id like \"sales@yourcompany.com\"", - "fieldname": "global", + "fieldname": "is_global", "fieldtype": "Check", "label": "Global", "permlevel": 0, @@ -62,15 +62,15 @@ }, { "allow_on_submit": 0, - "default": "1", + "default": "", "description": "Check this to pull emails from your mailbox", - "fieldname": "enabled", + "fieldname": "enable_incoming", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 1, - "label": "Enabled", + "label": "Enable Incoming", "no_copy": 0, "permlevel": 0, "precision": "", @@ -125,7 +125,7 @@ "print_hide": 0, "read_only": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0 }, @@ -151,20 +151,20 @@ { "allow_on_submit": 0, "description": "e.g. pop.gmail.com", - "fieldname": "pop3_mail_server", + "fieldname": "pop3_server", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 1, - "label": "POP3 Mail Server", + "label": "POP3 Server", "no_copy": 0, "permlevel": 0, "precision": "", "print_hide": 0, "read_only": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0 }, @@ -204,6 +204,7 @@ "precision": "" }, { + "default": "1", "fieldname": "enable_outgoing", "fieldtype": "Check", "label": "Enable Outgoing", @@ -337,6 +338,20 @@ "reqd": 0, "search_index": 0, "set_only_once": 0 + }, + { + "fieldname": "set_footer", + "fieldtype": "Section Break", + "label": "Set Footer", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "footer", + "fieldtype": "Text Editor", + "label": "Footer", + "permlevel": 0, + "precision": "" } ], "hide_heading": 0, @@ -347,7 +362,7 @@ "is_submittable": 0, "issingle": 0, "istable": 0, - "modified": "2014-09-11 15:42:06.931247", + "modified": "2014-09-15 12:01:38.649639", "modified_by": "Administrator", "module": "Email", "name": "Email Account", diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index ca70aedc66..50e2048c85 100644 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -2,7 +2,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +import frappe, os from frappe import _ from frappe.model.document import Document from frappe.utils import validate_email_add, cint @@ -10,24 +10,37 @@ from frappe.email.smtp import SMTPServer from frappe.email.receive import POP3Server, Email class EmailAccount(Document): + def autoname(self): + if not self.email_account_name: + self.email_account_name = self.email_id.split("@", 1)[0]\ + .replace("_", " ").replace(".", " ").replace("-", " ").title() + + if self.service: + self.email_account_name = self.email_account_name + " " + self.service + + self.name = self.email_account_name + def validate(self): if self.email_id and not validate_email_add(self.email_id): frappe.throw(_("{0} is not a valid email id").format(self.email_id), frappe.InvalidEmailAddressError) self.there_must_be_atleast_one_default() - self.check_smtp() - self.append_to_must_have_subject_and_status() - if self.enabled: + if frappe.local.flags.in_patch or frappe.local.flags.in_test: + return + + if self.enable_incoming: self.get_pop3() + self.check_smtp() + def on_update(self): self.there_must_be_only_one_default() def there_must_be_atleast_one_default(self): if not frappe.db.get_value("Email Account", {"is_default": 1}): - if not self.is_default: + if not self.is_default and (self.is_global and self.enable_outgoing): self.is_default = 1 frappe.msgprint(_("Setting as Default")) @@ -42,15 +55,13 @@ class EmailAccount(Document): email_account.is_default = 0 email_account.save() - def append_to_must_have_subject_and_status(self): - if self.append_to: - meta = frappe.get_meta(self.append_to) - if not (meta.has_field("subject") and meta.has_field("status")): - frappe.throw(_("Append To DocType must have fields 'Subject' and 'Status'")) - def check_smtp(self): if self.enable_outgoing and self.smtp_server \ and not frappe.local.flags.in_patch: + + if not self.smtp_server: + frappe.throw(_("{0} is required").format("SMTP Server")) + SMTPServer(login = self.email_id, password = self.password, server = self.smtp_server, @@ -60,20 +71,26 @@ class EmailAccount(Document): def get_pop3(self): args = { - "host": self.smtp_server, + "host": self.pop3_server, "use_ssl": self.use_ssl, "username": self.email_id, "password": self.password } - pop3 = POP3Server(args) + if not self.pop3_server: + frappe.throw(_("{0} is required").format("POP3 Server")) + + pop3 = POP3Server(frappe._dict(args)) pop3.connect() return pop3 def receive(self): - if self.enabled: - pop3 = self.get_pop3() - incoming_mails = pop3.get_messages() + if self.enable_incoming: + if frappe.local.flags.in_test: + incoming_mails = self.get_test_mails() + else: + pop3 = self.get_pop3() + incoming_mails = pop3.get_messages() for raw in incoming_mails: email = Email(raw) @@ -85,7 +102,7 @@ class EmailAccount(Document): "sent_or_received": "Received", "sender_full_name": email.from_real_name, "sender": email.from_email, - "recipients": email.get("To"), + "recipients": email.mail.get("To"), "email_account": self.name }) @@ -96,8 +113,11 @@ class EmailAccount(Document): # save attachments email.save_attachments_in_doc(communication) + if self.enable_auto_reply: + self.send_auto_reply(communication) + def set_thread(self, communication, email): - in_reply_to = (email.get("In-Reply-To") or "").strip(" <>") + in_reply_to = (email.mail.get("In-Reply-To") or "").strip(" <>") parent = None if in_reply_to: if "@" in in_reply_to: @@ -112,11 +132,15 @@ class EmailAccount(Document): parent = frappe.get_doc(parent.reference_doctype, parent.reference_name) + if not parent and self.append_to: # no parent found, but must be tagged # insert parent type doc parent = self.new_doc(self.append_to) - parent.subject = email.subject + + if parent.meta.get_field("subject"): + parent.subject = email.subject + parent.ignore_mandatory = True parent.insert(ignore_permissions=True) @@ -126,3 +150,23 @@ class EmailAccount(Document): if parent: communication.reference_doctype = parent.doctype communication.reference_name = parent.name + + def send_auto_reply(self, communication): + if self.auto_reply_message: + frappe.sendmail(recipients = [communication.from_email], + sender = self.email_id, + subject = _("Re: ") + communication.subject, + content = self.auto_reply_message or\ + frappe.render_template("templates/emails/auto_reply.html", {}), + bulk=True) + + def get_test_mails(self): + incoming_mails = [] + with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-1.raw"), "r") as f: + incoming_mails.append(f.read()) + + return incoming_mails + +def sync_emails(self): + for email_account in frappe.get_list("Email Account", filters={"enable_incoming": 1}): + frappe.tasks.pull_from_email_account.delay(frappe.local.site, email_account.name) diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index c077005649..28e30fbcb5 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -6,5 +6,22 @@ import unittest test_records = frappe.get_test_records('Email Account') +from frappe.core.doctype.communication.communication import make + class TestEmailAccount(unittest.TestCase): - pass + def test_incoming(self): + frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'") + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + email_account.receive() + + comm = frappe.get_doc("Communication", {"sender": "test_sender@example.com"}) + self.assertTrue("test_receiver@example.com" in comm.recipients) + + def test_outgoing(self): + make(subject = "Test", content="test content", recipients="test_receiver@example.com", + send_email=True) + + self.assertTrue(frappe.flags.sent_mail) + + diff --git a/frappe/email/doctype/email_account/test_records.json b/frappe/email/doctype/email_account/test_records.json index 9213e730c2..1cec4dc1be 100644 --- a/frappe/email/doctype/email_account/test_records.json +++ b/frappe/email/doctype/email_account/test_records.json @@ -1,6 +1,18 @@ [ { + "is_default": 1, + "is_global": 1, "doctype": "Email Account", - "name": "_Test Email Account 1" + "email_account_name": "_Test Email Account 1", + "enable_outgoing": 1, + "smtp_server": "test.example.com", + "email_id": "test@example.com", + "password": "password", + "add_signature": 1, + "signature": "\nBest Wishes\nTest Signature", + "enable_auto_reply": 1, + "auto_reply_message": "", + "enable_incoming": 1, + "pop3_server": "pop.test.example.com" } ] diff --git a/frappe/email/doctype/outgoing_email_settings/__init__.py b/frappe/email/doctype/outgoing_email_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/email/doctype/outgoing_email_settings/outgoing_email_settings.json b/frappe/email/doctype/outgoing_email_settings/outgoing_email_settings.json deleted file mode 100644 index 717367a977..0000000000 --- a/frappe/email/doctype/outgoing_email_settings/outgoing_email_settings.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "allow_copy": 1, - "creation": "2014-03-03 19:48:01", - "description": "Email Settings for Outgoing and Incoming Emails.", - "docstatus": 0, - "doctype": "DocType", - "fields": [ - { - "fieldname": "enabled", - "fieldtype": "Check", - "label": "Enabled", - "permlevel": 0 - }, - { - "depends_on": "eval:doc.enabled", - "fieldname": "section_break_2", - "fieldtype": "Section Break", - "label": "Server & Credentials", - "permlevel": 0 - }, - { - "description": "SMTP Server (e.g. smtp.gmail.com)", - "fieldname": "mail_server", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Outgoing Mail Server", - "permlevel": 0 - }, - { - "description": "[?]", - "fieldname": "use_ssl", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Use TLS", - "permlevel": 0 - }, - { - "description": "If non standard port (e.g. 587)", - "fieldname": "mail_port", - "fieldtype": "Int", - "in_list_view": 1, - "label": "Port", - "permlevel": 0 - }, - { - "fieldname": "cb0", - "fieldtype": "Column Break", - "permlevel": 0 - }, - { - "description": "Set Login and Password if authentication is required.", - "fieldname": "mail_login", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Login Id", - "permlevel": 0 - }, - { - "description": "Check this if you want to send emails as this id only (in case of restriction by your email provider).", - "fieldname": "always_use_login_id_as_sender", - "fieldtype": "Check", - "label": "Always use above Login Id as sender", - "permlevel": 0 - }, - { - "fieldname": "mail_password", - "fieldtype": "Password", - "label": "Mail Password", - "permlevel": 0 - }, - { - "description": "System generated mails will be sent from this email id.", - "fieldname": "auto_email_id", - "fieldtype": "Data", - "label": "Auto Email Id", - "permlevel": 0 - }, - { - "fieldname": "section_break_15", - "fieldtype": "Section Break", - "label": "Email Footer", - "permlevel": 0 - }, - { - "default": "
Sent via \n\tFrappe
", - "fieldname": "footer", - "fieldtype": "Text Editor", - "label": "", - "permlevel": 0, - "reqd": 0 - } - ], - "icon": "icon-cog", - "idx": 1, - "in_create": 1, - "issingle": 1, - "modified": "2014-07-17 08:08:00.483392", - "modified_by": "Administrator", - "module": "Email", - "name": "Outgoing Email Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "permlevel": 0, - "read": 1, - "role": "System Manager", - "write": 1 - } - ] -} diff --git a/frappe/email/doctype/outgoing_email_settings/outgoing_email_settings.py b/frappe/email/doctype/outgoing_email_settings/outgoing_email_settings.py deleted file mode 100644 index fda44b6154..0000000000 --- a/frappe/email/doctype/outgoing_email_settings/outgoing_email_settings.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _, throw -from frappe.utils import validate_email_add -from frappe.model.document import Document - -class OutgoingEmailSettings(Document): - - def validate(self): - if self.auto_email_id and not validate_email_add(self.auto_email_id): - throw(_("{0} is not a valid email id").format(self.auto_email_id), frappe.InvalidEmailAddressError) - - if self.mail_server and not frappe.local.flags.in_patch: - from frappe.utils import cint - from frappe.email.smtp import SMTPServer - smtpserver = SMTPServer(login = self.mail_login, - password = self.mail_password, - server = self.mail_server, - port = cint(self.mail_port), - use_ssl = cint(self.use_ssl) - ) - - # exceptions are handled in session connect - sess = smtpserver.sess - -def get_mail_footer(): - return frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "footer") or "" diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index faceb84540..2ef2f45ef9 100644 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -3,19 +3,20 @@ from __future__ import unicode_literals import frappe -from frappe import msgprint, throw, _ -from frappe.utils import scrub_urls, get_url +from frappe import throw, _ from frappe.utils.pdf import get_pdf +from frappe.email.smtp import get_outgoing_email_account +from frappe.utils import get_url, scrub_urls import email.utils from markdown2 import markdown - def get_email(recipients, sender='', msg='', subject='[No Subject]', - text_content = None, footer=None, print_html=None, formatted=None, attachments=None): + text_content = None, footer=None, print_html=None, formatted=None, attachments=None, + content=None): """send an html email as multipart with attachments and all""" emailobj = EMail(sender, recipients, subject) - msg = markdown(msg) - emailobj.set_html(msg, text_content, footer=footer, print_html=print_html, formatted=formatted) + msg = markdown(content or msg) + emailobj.set_html(content or msg, text_content, footer=footer, print_html=print_html, formatted=formatted) if isinstance(attachments, dict): attachments = [attachments] @@ -166,14 +167,7 @@ class EMail: return email if not self.sender: - self.sender = frappe.db.get_value('Outgoing Email Settings', None, - 'auto_email_id') or frappe.conf.get('auto_email_id') or None - if not self.sender: - msg = _("Please specify 'Auto Email Id' in Setup > Outgoing Email Settings") - msgprint(msg) - if not "expires_on" in frappe.conf: - msgprint(_("Alternatively, you can also specify 'auto_email_id' in site_config.json")) - raise frappe.ValidationError, msg + self.sender = get_outgoing_email_account().email_id self.sender = _validate(self.sender) self.reply_to = _validate(self.reply_to) @@ -223,11 +217,16 @@ def get_footer(footer=None): """append a footer (signature)""" footer = footer or "" - # hooks - for f in frappe.get_hooks("mail_footer"): - # mail_footer could be a function that returns a value - mail_footer = frappe.get_attr(f) - footer += (mail_footer if isinstance(mail_footer, basestring) else mail_footer()) + email_account = get_outgoing_email_account() + + if email_account.add_signature and email_account.signature: + footer += email_account.signature + + if email_account.footer: + footer += email_account.footer + else: + for default_mail_footer in frappe.get_hooks("default_mail_footer"): + footer += default_mail_footer footer += "" diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 41c9dc0829..250a0bf473 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -3,8 +3,9 @@ from __future__ import unicode_literals import time -import poplib +import _socket, poplib import frappe +from frappe import _ from frappe.utils import extract_email_id, convert_utc_to_user_timezone, now, cint from frappe.utils.scheduler import log @@ -37,11 +38,11 @@ class POP3Server: self.pop.user(self.settings.username) self.pop.pass_(self.settings.password) - except _socket.error, e: + except _socket.error: # Invalid mail server -- due to refusing connection frappe.msgprint(_('Invalid Mail Server. Please rectify and try again.')) raise - except poplib.error_proto, e: + except poplib.error_proto: frappe.msgprint(_('Invalid User Name or Support Password. Please rectify and try again.')) raise @@ -132,7 +133,7 @@ class POP3Server: if not incoming_mail: try: # retrieve headers - incoming_mail = EMail(b'\n'.join(self.pop.top(msg_num, 5)[1])) + incoming_mail = Email(b'\n'.join(self.pop.top(msg_num, 5)[1])) except: pass @@ -232,7 +233,7 @@ class Email: from frappe.utils.file_manager import save_file, MaxFileSizeReachedError for attachment in self.attachments: try: - fid = save_file(attachment['filename'], attachment['content'], + save_file(attachment['filename'], attachment['content'], doc.doctype, doc.name) except MaxFileSizeReachedError: # WARNING: bypass max file size exception diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py index 6c04e0cb5f..fcc177e3e7 100644 --- a/frappe/email/smtp.py +++ b/frappe/email/smtp.py @@ -15,6 +15,10 @@ def send(email, as_bulk=False): frappe.msgprint(_("Emails are muted")) return + if frappe.flags.in_test: + frappe.flags.sent_mail = email.as_string() + return + try: smtpserver = SMTPServer() if hasattr(smtpserver, "always_use_login_id_as_sender") and \ @@ -33,34 +37,57 @@ def send(email, as_bulk=False): frappe.msgprint(_("Invalid recipient address")) raise +def get_outgoing_email_account(raise_exception_not_set=True): + if not getattr(frappe.local, "outgoing_email_account", None): + email_account = frappe.db.get_value("Email Account", { + "owner": frappe.session.user, "enable_outgoing": 1}) + + if not email_account: + email_account = frappe.db.get_value('Email Account', {"is_default": 1}) + + if not email_account and not raise_exception_not_set: + return None + + if not email_account: + frappe.throw(_("Please setup default Email Account from Setup > Email > Email Account")) + + frappe.local.outgoing_email_account = frappe.get_doc("Email Account", email_account) + + return frappe.local.outgoing_email_account + class SMTPServer: def __init__(self, login=None, password=None, server=None, port=None, use_ssl=None): # get defaults from mail settings - try: - self.email_settings = frappe.get_doc('Outgoing Email Settings', 'Outgoing Email Settings') - except frappe.DoesNotExistError: - self.email_settings = None self._sess = None + self.email_account = None if server: self.server = server self.port = port self.use_ssl = cint(use_ssl) self.login = login self.password = password - elif self.email_settings and cint(self.email_settings.enabled): - self.server = self.email_settings.mail_server - self.port = self.email_settings.mail_port - self.use_ssl = cint(self.email_settings.use_ssl) - self.login = self.email_settings.mail_login - self.password = self.email_settings.mail_password - self.always_use_login_id_as_sender = self.email_settings.always_use_login_id_as_sender + else: - self.server = frappe.conf.get("mail_server") or "" - self.port = frappe.conf.get("mail_port") or None - self.use_ssl = cint(frappe.conf.get("use_ssl") or 0) - self.login = frappe.conf.get("mail_login") or "" - self.password = frappe.conf.get("mail_password") or "" + self.setup_from_user_or_default_outgoing() + + # from config + if not self.server: + self.server = frappe.conf.get("mail_server") or "" + self.port = frappe.conf.get("mail_port") or None + self.use_ssl = cint(frappe.conf.get("use_ssl") or 0) + self.login = frappe.conf.get("mail_login") or "" + self.password = frappe.conf.get("mail_password") or "" + + def setup_from_user_or_default_outgoing(self): + self.email_account = get_outgoing_email_account(raise_exception_not_set=False) + if self.email_account: + self.server = self.email_account.smtp_server + self.login = self.email_account.email_id + self.password = self.email_account.password + self.port = self.email_account.smtp_port + self.use_ssl = self.email_account.use_tls + @property def sess(self): @@ -70,7 +97,7 @@ class SMTPServer: # check if email server specified if not self.server: - err_msg = _('Outgoing Mail Server not specified') + err_msg = _('Email Account not setup. Please create a new Email Account from Setup > Email > Email Account') frappe.msgprint(err_msg) raise frappe.OutgoingEmailError, err_msg diff --git a/frappe/hooks.py b/frappe/hooks.py index fadf9b760b..6d4a6ff3a0 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -83,7 +83,10 @@ doc_events = { } scheduler_events = { - "all": ["frappe.email.bulk.flush"], + "all": [ + "frappe.email.bulk.flush", + "frappe.tasks.pull_emails" + ], "daily": [ "frappe.email.bulk.clear_outbox", "frappe.core.doctype.notification_count.notification_count.clear_notifications", @@ -95,5 +98,3 @@ scheduler_events = { "frappe.website.doctype.website_group.website_group.clear_event_cache" ] } - -mail_footer = "frappe.email.doctype.outgoing_email_settings.outgoing_email_settings.get_mail_footer" diff --git a/frappe/model/document.py b/frappe/model/document.py index 0097d392d6..814f435720 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -136,8 +136,8 @@ class Document(BaseDocument): self._set_defaults() self._set_docstatus_user_and_timestamp() self.check_if_latest() - self.set_new_name() self.run_method("before_insert") + self.set_new_name() self.set_parent_in_children() self.set("__in_insert", True) diff --git a/frappe/patches/v4_1/enable_outgoing_email_settings.py b/frappe/patches/v4_1/enable_outgoing_email_settings.py index 472a8109e3..fadf2b85e8 100644 --- a/frappe/patches/v4_1/enable_outgoing_email_settings.py +++ b/frappe/patches/v4_1/enable_outgoing_email_settings.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe def execute(): + return frappe.reload_doc("core", "doctype", "outgoing_email_settings") if (frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "mail_server") or "").strip(): frappe.db.set_value("Outgoing Email Settings", "Outgoing Email Settings", "enabled", 1) diff --git a/frappe/patches/v4_1/enable_print_as_pdf.py b/frappe/patches/v4_1/enable_print_as_pdf.py index 877a2b61f7..5d96c0030b 100644 --- a/frappe/patches/v4_1/enable_print_as_pdf.py +++ b/frappe/patches/v4_1/enable_print_as_pdf.py @@ -15,12 +15,11 @@ def execute(): pass else: # if someone has already configured in Outgoing Email Settings - outgoing_email_settings = frappe.db.get_singles_dict("Outgoing Email Settings") - if "send_print_as_pdf" in outgoing_email_settings: + # outgoing_email_settings = frappe.db.get_singles_dict("Outgoing Email Settings") + if False: # "send_print_as_pdf" in outgoing_email_settings: print_settings.send_print_as_pdf = outgoing_email_settings.send_print_as_pdf print_settings.pdf_page_size = outgoing_email_settings.pdf_page_size else: print_settings.send_print_as_pdf = 1 - print_settings.save() diff --git a/frappe/patches/v5_0/v4_to_v5.py b/frappe/patches/v5_0/v4_to_v5.py index 666991e3ac..d70c6abef7 100644 --- a/frappe/patches/v5_0/v4_to_v5.py +++ b/frappe/patches/v5_0/v4_to_v5.py @@ -7,7 +7,7 @@ def execute(): ("desk", ("event", "event_role", "event_user", "todo", "feed", "note", "note_user")), ("email", ("bulk_email", "email_alert", "email_alert_recipient", - "outgoing_email_settings", "standard_reply")), + "standard_reply")), ("geo", ("country", "currency")), ("print", ("letter_head", "print_format", "print_settings")) ) diff --git a/frappe/public/js/frappe/form/footer/comments.js b/frappe/public/js/frappe/form/footer/comments.js index dfc0400ba3..a95af2bf61 100644 --- a/frappe/public/js/frappe/form/footer/comments.js +++ b/frappe/public/js/frappe/form/footer/comments.js @@ -132,7 +132,7 @@ frappe.ui.form.Comments = Class.extend({ } // icon centering -- pixed perfect - if(in_list(["Comment", "Email"], c.comment_type)) { + if(in_list(["Comment", "Email", "Assignment Completed"], c.comment_type)) { c.padding = "padding-left: 8px;"; } else if(in_list(["Created"], c.comment_type)) { c.padding = "padding-left: 9px;"; diff --git a/frappe/tasks.py b/frappe/tasks.py index 1c9dcf0e5e..bd9add86c8 100644 --- a/frappe/tasks.py +++ b/frappe/tasks.py @@ -108,3 +108,13 @@ def enqueue_events_for_site(site): enqueue_events(site) finally: frappe.destroy() + +@celery_task() +def pull_from_email_account(site, email_account): + try: + frappe.init(site=site) + frappe.connect(site=site) + email_account = frappe.get_doc("Email Account", email_account) + email_account.receive() + finally: + frappe.destroy() diff --git a/frappe/templates/includes/comments.py b/frappe/templates/includes/comments.py index 2b7e1e13e2..9ca9dc6e33 100644 --- a/frappe/templates/includes/comments.py +++ b/frappe/templates/includes/comments.py @@ -45,7 +45,6 @@ def add_comment(args=None): owner = frappe.db.get_value(comment.comment_doctype, comment.comment_docname, "owner") recipients = list(set(commentors if owner=="Administrator" else (commentors + [owner]))) - from frappe.email.bulk import send send(recipients=recipients, doctype='Comment', diff --git a/frappe/test_runner.py b/frappe/test_runner.py index 7010258130..7f15c29c87 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -115,8 +115,6 @@ def _add_test(path, filename, verbose, test_suite=None): test_suite.addTest(unittest.TestLoader().loadTestsFromModule(module)) def make_test_records(doctype, verbose=0, force=False): - frappe.flags.mute_emails = True - if not frappe.db: frappe.connect()