diff --git a/config.json b/config.json
new file mode 100644
index 0000000000..51d656b777
--- /dev/null
+++ b/config.json
@@ -0,0 +1,40 @@
+{
+ "base_template": "lib/website/templates/base.html",
+ "framework_version": "3.9.0",
+ "modules": {
+ "Calendar": {
+ "color": "#2980b9",
+ "icon": "icon-calendar",
+ "label": "Calendar",
+ "link": "Calendar/Event",
+ "type": "view"
+ },
+ "Finder": {
+ "color": "#14C7DE",
+ "icon": "icon-folder-open",
+ "label": "Finder",
+ "link": "finder",
+ "type": "page"
+ },
+ "Messages": {
+ "color": "#9b59b6",
+ "icon": "icon-comments",
+ "label": "Messages",
+ "link": "messages",
+ "type": "page"
+ },
+ "To Do": {
+ "color": "#f1c40f",
+ "icon": "icon-check",
+ "label": "To Do",
+ "link": "todo",
+ "type": "page"
+ },
+ "Website": {
+ "color": "#16a085",
+ "icon": "icon-globe",
+ "link": "website-home",
+ "type": "module"
+ }
+ }
+}
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 6ee540199c..5478e1d76d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -18,4 +18,5 @@ slugify
termcolor
werkzeug
semantic_version
+lxml
inlinestyler
diff --git a/webnotes/__init__.py b/webnotes/__init__.py
index 6d41307682..05847949f3 100644
--- a/webnotes/__init__.py
+++ b/webnotes/__init__.py
@@ -220,6 +220,13 @@ def set_user(username):
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):
+ import webnotes.utils.email_lib
+ if as_markdown:
+ webnotes.utils.email_lib.sendmail_md(recipients, sender=sender, subject=subject, msg=message)
+ else:
+ webnotes.utils.email_lib.sendmail(recipients, sender=sender, subject=subject, msg=message)
+
logger = None
whitelisted = []
guest_methods = []
diff --git a/webnotes/core/doctype/communication/communication.py b/webnotes/core/doctype/communication/communication.py
index fe9cac3e4f..64199c4537 100644
--- a/webnotes/core/doctype/communication/communication.py
+++ b/webnotes/core/doctype/communication/communication.py
@@ -3,6 +3,13 @@
from __future__ import unicode_literals
import webnotes
+import json
+import urllib
+from email.utils import formataddr
+from webnotes.webutils import is_signup_enabled
+from webnotes.utils import get_url, cstr
+from webnotes.utils.email_lib.email_body import get_email
+from webnotes.utils.email_lib.smtp import send
class DocType():
def __init__(self, doc, doclist=[]):
@@ -36,13 +43,11 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
# since we are using fullname and email,
# if the fullname has any incompatible characters,formataddr can deal with it
try:
- import json
sender = json.loads(sender)
except ValueError:
pass
if isinstance(sender, (tuple, list)) and len(sender)==2:
- from email.utils import formataddr
sender = formataddr(sender)
comm = webnotes.new_bean('Communication')
@@ -76,7 +81,6 @@ def get_customer_supplier(args=None):
"""
Get Customer/Supplier, given a contact, if a unique match exists
"""
- import webnotes
if not args: args = webnotes.local.form_dict
if not args.get('contact'):
raise Exception, "Please specify a contact to fetch Customer/Supplier"
@@ -92,7 +96,6 @@ def get_customer_supplier(args=None):
return {}
def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', send_me_a_copy=False):
- from json import loads
footer = None
if sent_via:
@@ -105,7 +108,6 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s
footer = set_portal_link(sent_via, d)
- from webnotes.utils.email_lib.smtp import get_email
mail = get_email(d.recipients, sender=d.sender, subject=d.subject,
msg=d.content, footer=footer)
@@ -115,20 +117,17 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s
if print_html:
mail.add_attachment(name.replace(' ','').replace('/','-') + '.html', print_html)
- for a in loads(attachments):
+ for a in json.loads(attachments):
try:
mail.attach_file(a)
except IOError, e:
webnotes.msgprint("""Unable to find attachment %s. Please resend without attaching this file.""" % a,
raise_exception=True)
- mail.send()
+ send(mail)
def set_portal_link(sent_via, comm):
"""set portal link in footer"""
- from webnotes.webutils import is_signup_enabled
- from webnotes.utils import get_url, cstr
- import urllib
footer = None
@@ -143,29 +142,3 @@ def set_portal_link(sent_via, comm):
View this on our website""" % url
return footer
-
-def get_user(doctype, txt, searchfield, start, page_len, filters):
- from erpnext.controllers.queries import get_match_cond
- return webnotes.conn.sql("""select name, concat_ws(' ', first_name, middle_name, last_name)
- from `tabProfile`
- where ifnull(enabled, 0)=1
- and docstatus < 2
- and (%(key)s like "%(txt)s"
- or concat_ws(' ', first_name, middle_name, last_name) like "%(txt)s")
- %(mcond)s
- limit %(start)s, %(page_len)s """ % {'key': searchfield,
- 'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype, searchfield),
- 'start': start, 'page_len': page_len})
-
-def get_lead(doctype, txt, searchfield, start, page_len, filters):
- from erpnext.controllers.queries import get_match_cond
- return webnotes.conn.sql(""" select name, lead_name from `tabLead`
- where docstatus < 2
- and (%(key)s like "%(txt)s"
- or lead_name like "%(txt)s"
- or company_name like "%(txt)s")
- %(mcond)s
- order by lead_name asc
- limit %(start)s, %(page_len)s """ % {'key': searchfield,'txt': "%%%s%%" % txt,
- 'mcond':get_match_cond(doctype, searchfield), 'start': start,
- 'page_len': page_len})
\ No newline at end of file
diff --git a/webnotes/core/doctype/print_format/print_format.py b/webnotes/core/doctype/print_format/print_format.py
index 39c5b14527..c7f15f2b31 100644
--- a/webnotes/core/doctype/print_format/print_format.py
+++ b/webnotes/core/doctype/print_format/print_format.py
@@ -7,6 +7,8 @@ from webnotes import conf
import webnotes.utils
from webnotes.modules import get_doc_path
+standard_format = "templates/print_formats/standard.html"
+
class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d,dl
@@ -39,8 +41,9 @@ class DocType:
webnotes.clear_cache(doctype=self.doc.doc_type)
def get_args():
- if not webnotes.form_dict.doctype or not webnotes.form_dict.name \
- or not webnotes.form_dict.format:
+ if not webnotes.form_dict.format:
+ webnotes.form_dict.format = standard_format
+ if not webnotes.form_dict.doctype or not webnotes.form_dict.name:
return {
"body": """
<\/div>| )*$/, '');
// remove custom typography (use CSS!)
diff --git a/webnotes/templates/emails/.txt b/webnotes/templates/emails/.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/webnotes/templates/emails/new_message.html b/webnotes/templates/emails/new_message.html
new file mode 100644
index 0000000000..b51e7129f4
--- /dev/null
+++ b/webnotes/templates/emails/new_message.html
@@ -0,0 +1,5 @@
+
New Message
+
You have a new message from: {{ from }}
+
{{ message }}
+
+
Login and view in Browser
\ No newline at end of file
diff --git a/webnotes/templates/emails/new_user.html b/webnotes/templates/emails/new_user.html
new file mode 100644
index 0000000000..8c6b099c29
--- /dev/null
+++ b/webnotes/templates/emails/new_user.html
@@ -0,0 +1,10 @@
+
{{ title }}
+
Dear {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},
+
A new account has been created for you.
+
Your login id is: {{ user }}
+
Click on the button below to complete your registration and set a new password.
+
Complete Registration
+
+
You can also copy-paste this link in your browser {{ link }}
+
Thank you,
+{{ user_fullname }}
\ No newline at end of file
diff --git a/webnotes/templates/emails/password_reset.html b/webnotes/templates/emails/password_reset.html
new file mode 100644
index 0000000000..ecfd987191
--- /dev/null
+++ b/webnotes/templates/emails/password_reset.html
@@ -0,0 +1,6 @@
+
Password Reset
+
Dear {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},
+
Please click on the following link to update your new password:
+
{{ link }}
+
Thank you,
+{{ user_fullname }}
\ No newline at end of file
diff --git a/webnotes/templates/emails/password_update.html b/webnotes/templates/emails/password_update.html
new file mode 100644
index 0000000000..728c880ea3
--- /dev/null
+++ b/webnotes/templates/emails/password_update.html
@@ -0,0 +1,5 @@
+
Password Update Notification
+
Dear {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},
+
Your password has been updated. Here is your new password: {{ new_password }}
+
Thank you,
+{{ user_fullname }}
\ No newline at end of file
diff --git a/webnotes/templates/emails/html_email_template.html b/webnotes/templates/emails/standard.html
similarity index 97%
rename from webnotes/templates/emails/html_email_template.html
rename to webnotes/templates/emails/standard.html
index 8a7220842a..4ae7a35247 100644
--- a/webnotes/templates/emails/html_email_template.html
+++ b/webnotes/templates/emails/standard.html
@@ -25,9 +25,9 @@ body {
-webkit-text-size-adjust:none;
width: 100%!important;
height: 100%;
+ background-color: #f6f6f6;
}
-
/* -------------------------------------
ELEMENTS
------------------------------------- */
@@ -115,7 +115,7 @@ table.footer-wrap a{
------------------------------------- */
h1,h2,h3{
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; line-height: 1.1; margin-bottom:15px; color:#000;
- margin: 40px 0 10px;
+ margin: 20px 0 10px;
line-height: 1.2;
font-weight:200;
}
@@ -129,7 +129,10 @@ h2 {
h3 {
font-size: 22px;
}
-
+hr {
+ margin: 20px 0;
+ border-top: 1px solid #eee;
+}
p, ul, ol {
margin-bottom: 10px;
font-weight: normal;
diff --git a/webnotes/templates/print_formats/standard.html b/webnotes/templates/print_formats/standard.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/webnotes/test_runner.py b/webnotes/test_runner.py
index fd453c2084..dfb1d4a801 100644
--- a/webnotes/test_runner.py
+++ b/webnotes/test_runner.py
@@ -30,7 +30,7 @@ def main(app=None, module=None, doctype=None, verbose=False):
for doctype in module.test_dependencies:
make_test_records(doctype, verbose=verbose)
- test_suite.addTest(unittest.TestLoader().loadTestsFromModule(sys.modules[module]))
+ test_suite.addTest(unittest.TestLoader().loadTestsFromModule(module))
ret = unittest.TextTestRunner(verbosity=1+(verbose and 1 or 0)).run(test_suite)
else:
ret = run_all_tests(app, verbose)
diff --git a/webnotes/tests/test_email.py b/webnotes/tests/test_email.py
index 7ad906054b..1e2a39be79 100644
--- a/webnotes/tests/test_email.py
+++ b/webnotes/tests/test_email.py
@@ -4,10 +4,6 @@
from __future__ import unicode_literals
import os, sys
-sys.path.append('.')
-sys.path.append('lib/py')
-sys.path.append('erpnext')
-
import unittest, webnotes
from webnotes.test_runner import make_test_records
diff --git a/webnotes/translate.py b/webnotes/translate.py
index 10915b2ffc..0ba8e5d05d 100644
--- a/webnotes/translate.py
+++ b/webnotes/translate.py
@@ -288,4 +288,4 @@ def google_translate(lang, untranslated):
return dict(zip(untranslated, translated))
else:
print "unable to translate"
- return {}
\ No newline at end of file
+ return {}
diff --git a/webnotes/utils/__init__.py b/webnotes/utils/__init__.py
index 78dc573559..1614f0c66c 100644
--- a/webnotes/utils/__init__.py
+++ b/webnotes/utils/__init__.py
@@ -5,9 +5,11 @@
from __future__ import unicode_literals
from werkzeug.test import Client
+import os
+import re
+import urllib
import webnotes
-import os
no_value_fields = ['Section Break', 'Column Break', 'HTML', 'Table', 'FlexTable',
'Button', 'Image', 'Graph']
@@ -64,7 +66,6 @@ def extract_email_id(email):
def validate_email_add(email_str):
"""Validates the email string"""
email = extract_email_id(email_str)
- import re
return re.match("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", email.lower())
def get_request_site_address(full_address=False):
@@ -277,7 +278,6 @@ def dict_to_str(args, sep='&'):
"""
Converts a dictionary to URL
"""
- import urllib
t = []
for k in args.keys():
t.append(str(k)+'='+urllib.quote(str(args[k] or '')))
@@ -670,7 +670,6 @@ def strip_html(text):
"""
removes anything enclosed in and including <>
"""
- import re
return re.compile(r'<.*?>').sub('', text)
def escape_html(text):
@@ -831,7 +830,6 @@ def get_url(uri=None):
url = "http://" + subdomain
if uri:
- import urllib
url = urllib.basejoin(url, uri)
return url
@@ -896,14 +894,26 @@ def get_disk_usage():
return 0
err, out = execute_in_shell("du -hsm {files_path}".format(files_path=files_path))
return cint(out.split("\n")[-2].split("\t")[0])
-
-def expand_partial_links(html):
- import re
+
+def scrub_urls(html):
+ html = expand_relative_urls(html)
+ html = quote_urls(html)
+ return html
+
+def expand_relative_urls(html):
+ # expand relative urls
url = get_url()
if not url.endswith("/"): url += "/"
- return re.sub('(href|src){1}([\s]*=[\s]*[\'"]?)/*((?!http)[^\'" >]+)([\'"]?)',
- '\g<1>\g<2>{}\g<3>\g<4>'.format(url),
- html)
+ return re.sub('(href|src){1}([\s]*=[\s]*[\'"]?)((?!http)[^\'" >]+)([\'"]?)',
+ '\g<1>\g<2>{}\g<3>\g<4>'.format(url), html)
+
+def quote_urls(html):
+ def _quote_url(match):
+ groups = list(match.groups())
+ groups[2] = urllib.quote(groups[2], safe="/:")
+ return "".join(groups)
+ return re.sub('(href|src){1}([\s]*=[\s]*[\'"]?)((?:http)[^\'">]+)([\'"]?)',
+ _quote_url, html)
def touch_file(path):
with open(path, 'a'):
@@ -912,4 +922,4 @@ def touch_file(path):
def get_test_client():
from webnotes.app import application
- return Client(application)
\ No newline at end of file
+ return Client(application)
diff --git a/webnotes/utils/email_lib/__init__.py b/webnotes/utils/email_lib/__init__.py
index 2d1f4c6cf8..337c8764bf 100644
--- a/webnotes/utils/email_lib/__init__.py
+++ b/webnotes/utils/email_lib/__init__.py
@@ -5,6 +5,9 @@ from __future__ import unicode_literals
import webnotes
from webnotes import conf
+from webnotes.utils.email_lib.email_body import get_email
+from webnotes.utils.email_lib.smtp import send
+
def sendmail_md(recipients, sender=None, msg=None, subject=None):
"""send markdown email"""
import markdown2
@@ -12,12 +15,11 @@ def sendmail_md(recipients, sender=None, msg=None, subject=None):
def sendmail(recipients, sender='', msg='', subject='[No Subject]'):
"""send an html email as multipart with attachments and all"""
- from webnotes.utils.email_lib.smtp import get_email
- get_email(recipients, sender, msg, subject).send()
+
+ send(get_email(recipients, sender, msg, subject))
def sendmail_to_system_managers(subject, content):
- from webnotes.utils.email_lib.smtp import get_email
- get_email(get_system_managers(), None, content, subject).send()
+ send(get_email(get_system_managers(), None, content, subject))
@webnotes.whitelist()
def get_contact_list():
diff --git a/webnotes/utils/email_lib/bulk.py b/webnotes/utils/email_lib/bulk.py
index 1cece0bdc9..d8eb6f3158 100644
--- a/webnotes/utils/email_lib/bulk.py
+++ b/webnotes/utils/email_lib/bulk.py
@@ -3,31 +3,29 @@
from __future__ import unicode_literals
import webnotes
-from webnotes import msgprint, throw, _
-from webnotes.model.doc import Document
-from webnotes.utils import cint, get_url
+import HTMLParser
import urllib
+from webnotes import msgprint, throw, _
+from webnotes.utils.email_lib.smtp import SMTPServer, send
+from webnotes.utils.email_lib.email_body import get_email, get_formatted_html
+from webnotes.utils.email_lib.html2text import html2text
+from webnotes.utils import cint, get_url, nowdate
class BulkLimitCrossedError(webnotes.ValidationError): pass
def send(recipients=None, sender=None, doctype='Profile', email_field='email',
subject='[No Subject]', message='[No Content]', ref_doctype=None, ref_docname=None,
add_unsubscribe_link=True):
- """send bulk mail if not unsubscribed and within conf.bulk_mail_limit"""
- import webnotes
-
def is_unsubscribed(rdata):
- if not rdata: return 1
+ if not rdata:
+ return 1
return cint(rdata.unsubscribed)
def check_bulk_limit(new_mails):
- from webnotes import conf
- from webnotes.utils import nowdate
-
this_month = webnotes.conn.sql("""select count(*) from `tabBulk Email` where
month(creation)=month(%s)""" % nowdate())[0][0]
- monthly_bulk_mail_limit = conf.get('monthly_bulk_mail_limit') or 500
+ monthly_bulk_mail_limit = webnotes.conf.get('monthly_bulk_mail_limit') or 500
if this_month + len(recipients) > monthly_bulk_mail_limit:
throw("{bulk} ({limit}) {cross}".format(**{
@@ -36,10 +34,10 @@ def send(recipients=None, sender=None, doctype='Profile', email_field='email',
"cross": _("crossed")
}), exc=BulkLimitCrossedError)
- def update_message(doc):
- updated = message
+ def update_message(formatted, doc, add_unsubscribe_link):
+ updated = formatted
if add_unsubscribe_link:
- updated += """
""" % (get_url(),
@@ -49,6 +47,8 @@ def send(recipients=None, sender=None, doctype='Profile', email_field='email',
"type": doctype,
"email_field": email_field
}))
+
+ updated = updated.replace("", unsubscribe_link)
return updated
@@ -56,36 +56,33 @@ def send(recipients=None, sender=None, doctype='Profile', email_field='email',
if not sender or sender == "Administrator":
sender = webnotes.conn.get_value('Email Settings', None, 'auto_email_id')
check_bulk_limit(len(recipients))
-
- import HTMLParser
- from webnotes.utils.email_lib.html2text import html2text
- from webnotes.utils import expand_partial_links
try:
- message = expand_partial_links(message)
text_content = html2text(message)
except HTMLParser.HTMLParseError:
text_content = "[See html attachment]"
+ formatted = get_formatted_html(subject, message)
+
for r in filter(None, list(set(recipients))):
rdata = webnotes.conn.sql("""select * from `tab%s` where %s=%s""" % (doctype,
email_field, '%s'), (r,), as_dict=1)
doc = rdata and rdata[0] or {}
-
+
if not is_unsubscribed(doc):
# add to queue
- add(r, sender, subject, update_message(doc), text_content, ref_doctype, ref_docname)
+ add(r, sender, subject, update_message(formatted, doc, add_unsubscribe_link),
+ text_content, ref_doctype, ref_docname)
-def add(email, sender, subject, message, text_content=None, ref_doctype=None, ref_docname=None):
- """add to bulk mail queue"""
- from webnotes.utils.email_lib.smtp import get_email
-
- e = Document('Bulk Email')
+def add(email, sender, subject, formatted, text_content=None,
+ ref_doctype=None, ref_docname=None):
+ """add to bulk mail queue"""
+ e = webnotes.doc('Bulk Email')
e.sender = sender
e.recipient = email
try:
- e.message = get_email(email, sender=e.sender, msg=message, subject=subject,
+ e.message = get_email(email, sender=e.sender, formatted=formatted, subject=subject,
text_content = text_content).as_string()
except webnotes.ValidationError:
# bad email id - don't add to queue
@@ -116,15 +113,11 @@ def unsubscribe():
def flush(from_test=False):
"""flush email queue, every time: called from scheduler"""
- import webnotes
- from webnotes import conf
- from webnotes.utils.email_lib.smtp import SMTPServer, get_email
-
smptserver = SMTPServer()
auto_commit = not from_test
- if webnotes.flags.mute_emails or conf.get("mute_emails") or False:
+ if webnotes.flags.mute_emails or webnotes.conf.get("mute_emails") or False:
msgprint(_("Emails are muted"))
from_test = True
diff --git a/webnotes/utils/email_lib/email_body.py b/webnotes/utils/email_lib/email_body.py
new file mode 100644
index 0000000000..4675277039
--- /dev/null
+++ b/webnotes/utils/email_lib/email_body.py
@@ -0,0 +1,217 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+
+import webnotes
+
+from webnotes.utils import scrub_urls
+import email.utils
+from inlinestyler.utils import inline_css
+
+def get_email(recipients, sender='', msg='', subject='[No Subject]',
+ text_content = None, footer=None, formatted=None):
+ """send an html email as multipart with attachments and all"""
+ email = EMail(sender, recipients, subject)
+ if (not '
' in msg) and (not '
' in msg) and (not '
')
+ email.set_html(msg, text_content, footer=footer, formatted=formatted)
+
+ return email
+
+class EMail:
+ """
+ Wrapper on the email module. Email object represents emails to be sent to the client.
+ Also provides a clean way to add binary `FileData` attachments
+ Also sets all messages as multipart/alternative for cleaner reading in text-only clients
+ """
+ def __init__(self, sender='', recipients=[], subject='', alternative=0, reply_to=None):
+ from email.mime.multipart import MIMEMultipart
+ from email import Charset
+ Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
+
+ if isinstance(recipients, basestring):
+ recipients = recipients.replace(';', ',').replace('\n', '')
+ recipients = recipients.split(',')
+
+ # remove null
+ recipients = filter(None, (r.strip() for r in recipients))
+
+ self.sender = sender
+ self.reply_to = reply_to or sender
+ self.recipients = recipients
+ self.subject = subject
+
+ self.msg_root = MIMEMultipart('mixed')
+ self.msg_multipart = MIMEMultipart('alternative')
+ self.msg_root.attach(self.msg_multipart)
+ self.cc = []
+ self.html_set = False
+
+ def set_html(self, message, text_content = None, footer=None, formatted=None):
+ """Attach message in the html portion of multipart/alternative"""
+ if not formatted:
+ formatted = get_formatted_html(self.subject, message, footer)
+
+ # this is the first html part of a multi-part message,
+ # convert to text well
+ if not self.html_set:
+ if text_content:
+ self.set_text(text_content)
+ else:
+ self.set_html_as_text(message)
+
+ self.set_part_html(formatted)
+ self.html_set = True
+
+ def set_text(self, message):
+ """
+ Attach message in the text portion of multipart/alternative
+ """
+ from email.mime.text import MIMEText
+ part = MIMEText(message.encode('utf-8'), 'plain', 'utf-8')
+ self.msg_multipart.attach(part)
+
+ def set_part_html(self, message):
+ from email.mime.text import MIMEText
+ part = MIMEText(message.encode('utf-8'), 'html', 'utf-8')
+ self.msg_multipart.attach(part)
+
+ def set_html_as_text(self, html):
+ """return html2text"""
+ import HTMLParser
+ from webnotes.utils.email_lib.html2text import html2text
+ try:
+ self.set_text(html2text(html))
+ except HTMLParser.HTMLParseError:
+ pass
+
+ def set_message(self, message, mime_type='text/html', as_attachment=0, filename='attachment.html'):
+ """Append the message with MIME content to the root node (as attachment)"""
+ from email.mime.text import MIMEText
+
+ maintype, subtype = mime_type.split('/')
+ part = MIMEText(message, _subtype = subtype)
+
+ if as_attachment:
+ part.add_header('Content-Disposition', 'attachment', filename=filename)
+
+ self.msg_root.attach(part)
+
+ def attach_file(self, n):
+ """attach a file from the `FileData` table"""
+ from webnotes.utils.file_manager import get_file
+ res = get_file(n)
+ if not res:
+ return
+
+ self.add_attachment(res[0], res[1])
+
+ def add_attachment(self, fname, fcontent, content_type=None):
+ """add attachment"""
+ from email.mime.audio import MIMEAudio
+ from email.mime.base import MIMEBase
+ from email.mime.image import MIMEImage
+ from email.mime.text import MIMEText
+
+ import mimetypes
+ if not content_type:
+ content_type, encoding = mimetypes.guess_type(fname)
+
+ if content_type is None:
+ # No guess could be made, or the file is encoded (compressed), so
+ # use a generic bag-of-bits type.
+ content_type = 'application/octet-stream'
+
+ maintype, subtype = content_type.split('/', 1)
+ if maintype == 'text':
+ # Note: we should handle calculating the charset
+ if isinstance(fcontent, unicode):
+ fcontent = fcontent.encode("utf-8")
+ part = MIMEText(fcontent, _subtype=subtype, _charset="utf-8")
+ elif maintype == 'image':
+ part = MIMEImage(fcontent, _subtype=subtype)
+ elif maintype == 'audio':
+ part = MIMEAudio(fcontent, _subtype=subtype)
+ else:
+ part = MIMEBase(maintype, subtype)
+ part.set_payload(fcontent)
+ # Encode the payload using Base64
+ from email import encoders
+ encoders.encode_base64(part)
+
+ # Set the filename parameter
+ if fname:
+ part.add_header(b'Content-Disposition',
+ ("attachment; filename=%s" % fname).encode('utf-8'))
+
+ self.msg_root.attach(part)
+
+ def validate(self):
+ """validate the email ids"""
+ from webnotes.utils import validate_email_add
+ def _validate(email):
+ """validate an email field"""
+ if email and not validate_email_add(email):
+ webnotes.msgprint("%s is not a valid email id" % email,
+ raise_exception = 1)
+ return email
+
+ if not self.sender:
+ self.sender = webnotes.conn.get_value('Email Settings', None,
+ 'auto_email_id') or webnotes.conf.get('auto_email_id') or None
+ if not self.sender:
+ webnotes.msgprint("""Please specify 'Auto Email Id' \
+ in Setup > Email Settings""")
+ if not "expires_on" in webnotes.conf:
+ webnotes.msgprint("""Alternatively, \
+ you can also specify 'auto_email_id' in site_config.json""")
+ raise webnotes.ValidationError
+
+ self.sender = _validate(self.sender)
+ self.reply_to = _validate(self.reply_to)
+
+ for e in self.recipients + (self.cc or []):
+ _validate(e.strip())
+
+ def make(self):
+ """build into msg_root"""
+ self.msg_root['Subject'] = self.subject.encode("utf-8")
+ self.msg_root['From'] = self.sender.encode("utf-8")
+ self.msg_root['To'] = ', '.join([r.strip() for r in self.recipients]).encode("utf-8")
+ self.msg_root['Date'] = email.utils.formatdate()
+ if not self.reply_to:
+ self.reply_to = self.sender
+ self.msg_root['Reply-To'] = self.reply_to.encode("utf-8")
+ if self.cc:
+ self.msg_root['CC'] = ', '.join([r.strip() for r in self.cc]).encode("utf-8")
+
+ def as_string(self):
+ """validate, build message and convert to string"""
+ self.validate()
+ self.make()
+ return self.msg_root.as_string()
+
+def get_formatted_html(subject, message, footer=None):
+ message = scrub_urls(message)
+
+ return inline_css(webnotes.get_template("templates/emails/standard.html").render({
+ "content": message,
+ "footer": get_footer(footer),
+ "title": subject
+ }))
+
+def get_footer(footer=None):
+ """append a footer (signature)"""
+ footer = footer or ""
+
+ # control panel
+ footer += webnotes.conn.get_value('Control Panel',None,'mail_footer') or ''
+
+ # hooks
+ for f in webnotes.get_hooks("mail_footer"):
+ footer += webnotes.get_attr(f)
+
+ footer += ""
+
+ return footer
diff --git a/webnotes/utils/email_lib/smtp.py b/webnotes/utils/email_lib/smtp.py
index c302207142..3e0b4774a7 100644
--- a/webnotes/utils/email_lib/smtp.py
+++ b/webnotes/utils/email_lib/smtp.py
@@ -2,247 +2,43 @@
# MIT License. See license.txt
from __future__ import unicode_literals
-"""
-Sends email via outgoing server specified in "Control Panel"
-Allows easy adding of Attachments of "File" objects
-"""
import webnotes
-from webnotes import conf
-from webnotes import msgprint
-from webnotes.utils import cint, expand_partial_links
-import email.utils
-
-def get_email(recipients, sender='', msg='', subject='[No Subject]', text_content = None, footer=None):
- """send an html email as multipart with attachments and all"""
- email = EMail(sender, recipients, subject)
- if (not '
' in msg) and (not '
' in msg) and (not '
')
- email.set_html(msg, text_content, footer=footer)
-
- return email
-
-class EMail:
- """
- Wrapper on the email module. Email object represents emails to be sent to the client.
- Also provides a clean way to add binary `FileData` attachments
- Also sets all messages as multipart/alternative for cleaner reading in text-only clients
- """
- def __init__(self, sender='', recipients=[], subject='', alternative=0, reply_to=None):
- from email.mime.multipart import MIMEMultipart
- from email import Charset
- Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
-
- if isinstance(recipients, basestring):
- recipients = recipients.replace(';', ',').replace('\n', '')
- recipients = recipients.split(',')
-
- # remove null
- recipients = filter(None, (r.strip() for r in recipients))
-
- self.sender = sender
- self.reply_to = reply_to or sender
- self.recipients = recipients
- self.subject = subject
-
- self.msg_root = MIMEMultipart('mixed')
- self.msg_multipart = MIMEMultipart('alternative')
- self.msg_root.attach(self.msg_multipart)
- self.cc = []
- self.html_set = False
-
- def set_html(self, message, text_content = None, footer=None):
- """Attach message in the html portion of multipart/alternative"""
- message = message + self.get_footer(footer)
- message = expand_partial_links(message)
-
- # this is the first html part of a multi-part message,
- # convert to text well
- if not self.html_set:
- if text_content:
- self.set_text(text_content)
- else:
- self.set_html_as_text(message)
-
- self.set_part_html(message)
- self.html_set = True
-
- def set_text(self, message):
- """
- Attach message in the text portion of multipart/alternative
- """
- from email.mime.text import MIMEText
- part = MIMEText(message.encode('utf-8'), 'plain', 'utf-8')
- self.msg_multipart.attach(part)
-
- def set_part_html(self, message):
- from email.mime.text import MIMEText
- part = MIMEText(message.encode('utf-8'), 'html', 'utf-8')
- self.msg_multipart.attach(part)
-
- def set_html_as_text(self, html):
- """return html2text"""
- import HTMLParser
- from webnotes.utils.email_lib.html2text import html2text
- try:
- self.set_text(html2text(html))
- except HTMLParser.HTMLParseError:
- pass
-
- def set_message(self, message, mime_type='text/html', as_attachment=0, filename='attachment.html'):
- """Append the message with MIME content to the root node (as attachment)"""
- from email.mime.text import MIMEText
-
- maintype, subtype = mime_type.split('/')
- part = MIMEText(message, _subtype = subtype)
-
- if as_attachment:
- part.add_header('Content-Disposition', 'attachment', filename=filename)
-
- self.msg_root.attach(part)
-
- def get_footer(self, footer=None):
- """append a footer (signature)"""
- footer = footer or ""
- footer += webnotes.conn.get_value('Control Panel',None,'mail_footer') or ''
-
- other_footers = webnotes.get_hooks().mail_footer or []
-
- for f in other_footers:
- footer += f
-
- return footer
-
- def attach_file(self, n):
- """attach a file from the `FileData` table"""
- from webnotes.utils.file_manager import get_file
- res = get_file(n)
- if not res:
- return
-
- self.add_attachment(res[0], res[1])
+import smtplib
+import _socket
+from webnotes.utils import cint
+
+def send(email, as_bulk=False):
+ """send the message or add it to Outbox Email"""
+ if webnotes.flags.mute_emails or webnotes.conf.get("mute_emails") or False:
+ webnotes.msgprint("Emails are muted")
+ return
- def add_attachment(self, fname, fcontent, content_type=None):
- """add attachment"""
- from email.mime.audio import MIMEAudio
- from email.mime.base import MIMEBase
- from email.mime.image import MIMEImage
- from email.mime.text import MIMEText
-
- import mimetypes
- if not content_type:
- content_type, encoding = mimetypes.guess_type(fname)
-
- if content_type is None:
- # No guess could be made, or the file is encoded (compressed), so
- # use a generic bag-of-bits type.
- content_type = 'application/octet-stream'
-
- maintype, subtype = content_type.split('/', 1)
- if maintype == 'text':
- # Note: we should handle calculating the charset
- if isinstance(fcontent, unicode):
- fcontent = fcontent.encode("utf-8")
- part = MIMEText(fcontent, _subtype=subtype, _charset="utf-8")
- elif maintype == 'image':
- part = MIMEImage(fcontent, _subtype=subtype)
- elif maintype == 'audio':
- part = MIMEAudio(fcontent, _subtype=subtype)
- else:
- part = MIMEBase(maintype, subtype)
- part.set_payload(fcontent)
- # Encode the payload using Base64
- from email import encoders
- encoders.encode_base64(part)
+ try:
+ smtpserver = SMTPServer()
+ if hasattr(smtpserver, "always_use_login_id_as_sender") and \
+ cint(smtpserver.always_use_login_id_as_sender) and smtpserver.login:
+ if not email.reply_to:
+ email.reply_to = email.sender
+ email.sender = smtpserver.login
+
+ smtpserver.sess.sendmail(email.sender, email.recipients + (email.cc or []),
+ email.as_string())
- # Set the filename parameter
- if fname:
- part.add_header(b'Content-Disposition',
- ("attachment; filename=%s" % fname).encode('utf-8'))
-
- self.msg_root.attach(part)
-
- def validate(self):
- """validate the email ids"""
- from webnotes.utils import validate_email_add
- def _validate(email):
- """validate an email field"""
- if email and not validate_email_add(email):
- webnotes.msgprint("%s is not a valid email id" % email,
- raise_exception = 1)
- return email
-
- if not self.sender:
- self.sender = webnotes.conn.get_value('Email Settings', None,
- 'auto_email_id') or conf.get('auto_email_id') or None
- if not self.sender:
- webnotes.msgprint("""Please specify 'Auto Email Id' \
- in Setup > Email Settings""")
- if not "expires_on" in conf:
- webnotes.msgprint("""Alternatively, \
- you can also specify 'auto_email_id' in conf.py""")
- raise webnotes.ValidationError
-
- self.sender = _validate(self.sender)
- self.reply_to = _validate(self.reply_to)
-
- for e in self.recipients + (self.cc or []):
- _validate(e.strip())
-
- def make(self):
- """build into msg_root"""
- self.msg_root['Subject'] = self.subject.encode("utf-8")
- self.msg_root['From'] = self.sender.encode("utf-8")
- self.msg_root['To'] = ', '.join([r.strip() for r in self.recipients]).encode("utf-8")
- self.msg_root['Date'] = email.utils.formatdate()
- if not self.reply_to:
- self.reply_to = self.sender
- self.msg_root['Reply-To'] = self.reply_to.encode("utf-8")
- if self.cc:
- self.msg_root['CC'] = ', '.join([r.strip() for r in self.cc]).encode("utf-8")
-
- def as_string(self):
- """validate, build message and convert to string"""
- self.validate()
- self.make()
- return self.msg_root.as_string()
-
- def send(self, as_bulk=False):
- """send the message or add it to Outbox Email"""
- if webnotes.flags.mute_emails or conf.get("mute_emails") or False:
- webnotes.msgprint("Emails are muted")
- return
-
-
- import smtplib
- try:
- smtpserver = SMTPServer()
- if hasattr(smtpserver, "always_use_login_id_as_sender") and \
- cint(smtpserver.always_use_login_id_as_sender) and smtpserver.login:
- if not self.reply_to:
- self.reply_to = self.sender
- self.sender = smtpserver.login
-
- smtpserver.sess.sendmail(self.sender, self.recipients + (self.cc or []),
- self.as_string())
-
- except smtplib.SMTPSenderRefused:
- webnotes.msgprint("""Invalid Outgoing Mail Server's Login Id or Password. \
- Please rectify and try again.""")
- raise
- except smtplib.SMTPRecipientsRefused:
- webnotes.msgprint("""Invalid Recipient (To) Email Address. \
- Please rectify and try again.""")
- raise
+ except smtplib.SMTPSenderRefused:
+ webnotes.msgprint("""Invalid Outgoing Mail Server's Login Id or Password. \
+ Please rectify and try again.""")
+ raise
+ except smtplib.SMTPRecipientsRefused:
+ webnotes.msgprint("""Invalid Recipient (To) Email Address. \
+ Please rectify and try again.""")
+ raise
class SMTPServer:
def __init__(self, login=None, password=None, server=None, port=None, use_ssl=None):
- import webnotes.model.doc
- from webnotes.utils import cint
-
# get defaults from control panel
try:
- es = webnotes.model.doc.Document('Email Settings','Email Settings')
+ es = webnotes.doc('Email Settings','Email Settings')
except webnotes.DoesNotExistError:
es = None
@@ -261,11 +57,11 @@ class SMTPServer:
self.password = es.mail_password
self.always_use_login_id_as_sender = es.always_use_login_id_as_sender
else:
- self.server = conf.get("mail_server") or ""
- self.port = conf.get("mail_port") or None
- self.use_ssl = cint(conf.get("use_ssl") or 0)
- self.login = conf.get("mail_login") or ""
- self.password = conf.get("mail_password") or ""
+ self.server = webnotes.conf.get("mail_server") or ""
+ self.port = webnotes.conf.get("mail_port") or None
+ self.use_ssl = cint(webnotes.conf.get("use_ssl") or 0)
+ self.login = webnotes.conf.get("mail_login") or ""
+ self.password = webnotes.conf.get("mail_password") or ""
@property
def sess(self):
@@ -273,10 +69,6 @@ class SMTPServer:
if self._sess:
return self._sess
- from webnotes.utils import cint
- import smtplib
- import _socket
-
# check if email server specified
if not self.server:
err_msg = 'Outgoing Mail Server not specified'
@@ -306,7 +98,7 @@ class SMTPServer:
# check if logged correctly
if ret[0]!=235:
- msgprint(ret[1])
+ webnotes.msgprint(ret[1])
raise webnotes.OutgoingEmailError, ret[1]
return self._sess