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": """

Error

Parameters doctype, name and format required

@@ -61,12 +64,12 @@ def get_args(): "comment": webnotes.session.user } -def get_html(doc, doclist): +def get_html(doc, doclist, print_format=None): from jinja2 import Environment from webnotes.core.doctype.print_format.print_format import get_print_format template = Environment().from_string(get_print_format(doc.doctype, - webnotes.form_dict.format)) + print_format or webnotes.form_dict.format)) doctype = webnotes.get_doctype(doc.doctype) args = { @@ -79,15 +82,18 @@ def get_html(doc, doclist): html = template.render(args) return html -def get_print_format(doctype, format): +def get_print_format(doctype, format_name): + if format_name==standard_format: + return format_name + # server, find template path = os.path.join(get_doc_path(webnotes.conn.get_value("DocType", doctype, "module"), - "Print Format", format), format + ".html") + "Print Format", format_name), format_name + ".html") if os.path.exists(path): with open(path, "r") as pffile: return pffile.read() else: - html = webnotes.conn.get_value("Print Format", format, "html") + html = webnotes.conn.get_value("Print Format", format_name, "html") if html: return html else: diff --git a/webnotes/core/doctype/profile/profile.js b/webnotes/core/doctype/profile/profile.js index e8751ca065..2ddf4459ba 100644 --- a/webnotes/core/doctype/profile/profile.js +++ b/webnotes/core/doctype/profile/profile.js @@ -34,7 +34,7 @@ cur_frm.cscript.user_image = function(doc) { } cur_frm.cscript.refresh = function(doc) { - if(!doc.__unsaved && doc.language !== wn.boot.profile.language) { + if(!doc.__unsaved && wn.languages && doc.language !== wn.boot.profile.language) { msgprint("Refreshing..."); window.location.reload(); } diff --git a/webnotes/core/doctype/profile/profile.py b/webnotes/core/doctype/profile/profile.py index ce7252b2c7..66fb92ebe8 100644 --- a/webnotes/core/doctype/profile/profile.py +++ b/webnotes/core/doctype/profile/profile.py @@ -2,7 +2,7 @@ # MIT License. See license.txt from __future__ import unicode_literals -import webnotes, json +import webnotes, json, os from webnotes.utils import cint, now, cstr from webnotes import throw, msgprint, _ from webnotes.auth import _update_password @@ -127,73 +127,21 @@ class DocType: (self.doc.first_name and " " or '') + (self.doc.last_name or '') def password_reset_mail(self, link): - """reset password""" - txt = """ -## %(title)s - -#### Password Reset - -Dear %(first_name)s, - -Please click on the following link to update your new password: - -%(link)s - -Thank you,
-%(user_fullname)s - """ - self.send_login_mail("Password Reset", - txt, {"link": link}) + self.send_login_mail("Password Reset", "templates/emails/password_reset.html", {"link": link}) def password_update_mail(self, password): - txt = """ -## %(title)s - -#### Password Update Notification - -Dear %(first_name)s, - -Your password has been updated. Here is your new password: %(new_password)s - -Thank you,
-%(user_fullname)s - """ - self.send_login_mail("Password Update", - txt, {"new_password": password}) - + self.send_login_mail("Password Update", "templates/emails/password_update.html", {"new_password": password}) def send_welcome_mail(self): - """send welcome mail to user with password and login url""" - from webnotes.utils import random_string, get_url self.doc.reset_password_key = random_string(32) link = get_url("/update-password?key=" + self.doc.reset_password_key) - - txt = """ -## %(title)s - -Dear %(first_name)s, - -A new account has been created for you. - -Your login id is: %(user)s -To complete your registration, please click on the link below: - -%(link)s - -Thank you,
-%(user_fullname)s - """ - self.send_login_mail("New Account", txt, - { "link": link }) - - def send_login_mail(self, subject, txt, add_args): + self.send_login_mail("Verify Your Account", "templates/emails/new_user.html", {"link": link}) + + def send_login_mail(self, subject, template, add_args): """send mail with login details""" - import os - - from webnotes.utils.email_lib import sendmail_md from webnotes.profile import get_user_fullname from webnotes.utils import get_url @@ -216,7 +164,8 @@ Thank you,
sender = webnotes.session.user not in ("Administrator", "Guest") and webnotes.session.user or None - sendmail_md(recipients=self.doc.email, sender=sender, subject=subject, msg=txt % args) + webnotes.sendmail(recipients=self.doc.email, sender=sender, subject=subject, + message=webnotes.get_template(template).render(args)) def a_system_manager_should_exist(self): if not self.get_other_system_managers(): diff --git a/webnotes/core/page/messages/messages.py b/webnotes/core/page/messages/messages.py index 903406e7a2..eb7b0012f4 100644 --- a/webnotes/core/page/messages/messages.py +++ b/webnotes/core/page/messages/messages.py @@ -89,23 +89,13 @@ def delete(arg=None): def notify(arg=None): from webnotes.utils import cstr, get_fullname, get_url - fn = get_fullname(webnotes.user.name) or webnotes.user.name - - url = get_url() - - message = '''You have a message from %s: - - %s - - To answer, please login to your erpnext account at \ - %s - ''' % (fn, arg['txt'], url, url) - - sender = webnotes.conn.get_value("Profile", webnotes.user.name, "email") \ - or webnotes.user.name - recipient = [webnotes.conn.get_value("Profile", arg["contact"], "email") \ - or arg["contact"]] - - from webnotes.utils.email_lib import sendmail - sendmail(recipient, sender, message, arg.get("subject") or "You have a message from %s" % (fn,)) - \ No newline at end of file + webnotes.sendmail(\ + recipients=[webnotes.conn.get_value("Profile", arg["contact"], "email") or arg["contact"]], + sender= webnotes.conn.get_value("Profile", webnotes.session.user, "email"), + subject="New Message from " + get_fullname(webnotes.user.name), + message=webnotes.get_template("templates/emails/new_message.html").render({ + "from": get_fullname(webnotes.user.name), + "message": arg['txt'], + "link": get_url() + }) + ) \ No newline at end of file diff --git a/webnotes/patches.txt b/webnotes/patches.txt index 77c888a2e5..d8781cab77 100644 --- a/webnotes/patches.txt +++ b/webnotes/patches.txt @@ -1,3 +1,5 @@ +execute:import inlinestyler # new requirement + execute:webnotes.reload_doc('core', 'doctype', 'doctype', force=True) #2014-01-24 execute:webnotes.reload_doc('core', 'doctype', 'docfield', force=True) #2013-13-26 execute:webnotes.reload_doc('core', 'doctype', 'docperm') #2013-13-26 diff --git a/webnotes/public/css/bootstrap.css b/webnotes/public/css/bootstrap.css index 5ad2417d8f..0daaedd464 100644 --- a/webnotes/public/css/bootstrap.css +++ b/webnotes/public/css/bootstrap.css @@ -1,10 +1,18 @@ /*! - * Bootstrap v3.0.3 (http://getbootstrap.com) + * Bootstrap v3.1.0 (http://getbootstrap.com) * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ -/*! normalize.css v2.1.3 | MIT License | git.io/normalize */ +/*! normalize.css v3.0.0 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} article, aside, details, @@ -21,8 +29,10 @@ summary { } audio, canvas, +progress, video { display: inline-block; + vertical-align: baseline; } audio:not([controls]) { display: none; @@ -32,29 +42,13 @@ audio:not([controls]) { template { display: none; } -html { - font-family: sans-serif; - - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; -} -body { - margin: 0; -} a { background: transparent; } -a:focus { - outline: thin dotted; -} a:active, a:hover { outline: 0; } -h1 { - margin: .67em 0; - font-size: 2em; -} abbr[title] { border-bottom: 1px dotted; } @@ -65,28 +59,14 @@ strong { dfn { font-style: italic; } -hr { - height: 0; - -moz-box-sizing: content-box; - box-sizing: content-box; +h1 { + margin: .67em 0; + font-size: 2em; } mark { color: #000; background: #ff0; } -code, -kbd, -pre, -samp { - font-family: monospace, serif; - font-size: 1em; -} -pre { - white-space: pre-wrap; -} -q { - quotes: "\201C" "\201D" "\2018" "\2019"; -} small { font-size: 80%; } @@ -110,28 +90,34 @@ svg:not(:root) { overflow: hidden; } figure { - margin: 0; + margin: 1em 40px; } -fieldset { - padding: .35em .625em .75em; - margin: 0 2px; - border: 1px solid #c0c0c0; +hr { + height: 0; + -moz-box-sizing: content-box; + box-sizing: content-box; } -legend { - padding: 0; - border: 0; +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; } button, input, +optgroup, select, textarea { margin: 0; - font-family: inherit; - font-size: 100%; + font: inherit; + color: inherit; } -button, -input { - line-height: normal; +button { + overflow: visible; } button, select { @@ -148,11 +134,23 @@ button[disabled], html input[disabled] { cursor: default; } +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; } +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} input[type="search"] { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; @@ -163,19 +161,29 @@ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } -button::-moz-focus-inner, -input::-moz-focus-inner { +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { padding: 0; border: 0; } textarea { overflow: auto; - vertical-align: top; +} +optgroup { + font-weight: bold; } table { border-spacing: 0; border-collapse: collapse; } +td, +th { + padding: 0; +} @media print { * { color: #000 !important; @@ -213,9 +221,6 @@ table { img { max-width: 100% !important; } - @page { - margin: 2cm .5cm; - } p, h2, h3 { @@ -296,6 +301,9 @@ a:focus { outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } +figure { + margin: 0; +} img { vertical-align: middle; } @@ -1620,6 +1628,7 @@ table th[class*="col-"] { } } fieldset { + min-width: 0; padding: 0; margin: 0; border: 0; @@ -1662,11 +1671,6 @@ select[multiple], select[size] { height: auto; } -select optgroup { - font-family: inherit; - font-size: inherit; - font-style: inherit; -} input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { @@ -1674,10 +1678,6 @@ input[type="checkbox"]:focus { outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } -input[type="number"]::-webkit-outer-spin-button, -input[type="number"]::-webkit-inner-spin-button { - height: auto; -} output { display: block; padding-top: 7px; @@ -1699,7 +1699,7 @@ output { border-radius: 4px; } .form-control:focus { - border-color: #66afe9; + border-color: #000; outline: 0; } .form-control:-moz-placeholder { @@ -1720,6 +1720,7 @@ output { fieldset[disabled] .form-control { cursor: not-allowed; background-color: #eee; + opacity: 1; } textarea.form-control { height: auto; @@ -1794,7 +1795,8 @@ select.input-sm { height: 30px; line-height: 30px; } -textarea.input-sm { +textarea.input-sm, +select[multiple].input-sm { height: auto; } .input-lg { @@ -1808,7 +1810,8 @@ select.input-lg { height: 46px; line-height: 46px; } -textarea.input-lg { +textarea.input-lg, +select[multiple].input-lg { height: auto; } .has-feedback { @@ -3312,7 +3315,10 @@ select.input-group-lg > .input-group-btn > .btn { } textarea.input-group-lg > .form-control, textarea.input-group-lg > .input-group-addon, -textarea.input-group-lg > .input-group-btn > .btn { +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { height: auto; } .input-group-sm > .form-control, @@ -3332,7 +3338,10 @@ select.input-group-sm > .input-group-btn > .btn { } textarea.input-group-sm > .form-control, textarea.input-group-sm > .input-group-addon, -textarea.input-group-sm > .input-group-btn > .btn { +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { height: auto; } .input-group-addon, @@ -3717,6 +3726,7 @@ textarea.input-group-sm > .input-group-btn > .btn { } .navbar-brand { float: left; + height: 20px; padding: 8px 15px; font-size: 18px; line-height: 20px; @@ -3725,11 +3735,6 @@ textarea.input-group-sm > .input-group-btn > .btn { .navbar-brand:focus { text-decoration: none; } -.navbar-brand > .glyphicon { - float: left; - margin-top: -2px; - margin-right: 5px; -} @media (min-width: 768px) { .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand { @@ -4834,8 +4839,8 @@ a.list-group-item-danger.active:focus { .panel > .panel-body + .table-responsive { border-top: 1px solid #ddd; } -.panel > .table > tbody:first-child th, -.panel > .table > tbody:first-child td { +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { border-top: 0; } .panel > .table-bordered, diff --git a/webnotes/public/css/font-awesome.css b/webnotes/public/css/font-awesome.css index 8de4012a86..1592b9cebd 100644 --- a/webnotes/public/css/font-awesome.css +++ b/webnotes/public/css/font-awesome.css @@ -32,6 +32,14 @@ font-weight: normal; font-style: normal; } + +@media screen and (-webkit-min-device-pixel-ratio:0) { + @font-face { + font-family: 'FontAwesome'; + src: url('../lib/css/font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg'); + } +} + /* FONT AWESOME CORE * -------------------------- */ [class^="icon-"], diff --git a/webnotes/public/css/less/webnotes.less b/webnotes/public/css/less/webnotes.less new file mode 100644 index 0000000000..17dd3d556f --- /dev/null +++ b/webnotes/public/css/less/webnotes.less @@ -0,0 +1,9 @@ +// include in bootstrap.less + +@font-family-sans-serif: Arial, sans-serif; +@input-border-focus: #000; +@alert-padding: 10px; + +label { + font-weight: normal; +} \ No newline at end of file diff --git a/webnotes/public/js/legacy/print_format.js b/webnotes/public/js/legacy/print_format.js index 4ed9be623e..b4bb3fe468 100644 --- a/webnotes/public/js/legacy/print_format.js +++ b/webnotes/public/js/legacy/print_format.js @@ -389,7 +389,7 @@ $.extend(_p, { lh = cstr(wn.boot.letter_heads[cur_frm.doc.letter_head]); } else if (cp.letter_head) { lh = cp.letter_head; - } + } return lh; }, diff --git a/webnotes/public/js/lib/bootstrap.min.js b/webnotes/public/js/lib/bootstrap.min.js index cf1170f6e4..1d4a4ed370 100755 --- a/webnotes/public/js/lib/bootstrap.min.js +++ b/webnotes/public/js/lib/bootstrap.min.js @@ -1,5 +1,5 @@ /*! - * Bootstrap v3.0.3 (http://getbootstrap.com) + * Bootstrap v3.1.0 (http://getbootstrap.com) * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ diff --git a/webnotes/public/js/wn/model/meta.js b/webnotes/public/js/wn/model/meta.js index dc70e9f238..cbdf182be0 100644 --- a/webnotes/public/js/wn/model/meta.js +++ b/webnotes/public/js/wn/model/meta.js @@ -100,13 +100,8 @@ $.extend(wn.meta, { }, get_print_formats: function(doctype) { - // if default print format is given, use it - var print_format_list = []; - if(locals.DocType[doctype].default_print_format) - print_format_list.push(locals.DocType[doctype].default_print_format) - - if(!in_list(print_format_list, "Standard")) - print_format_list.push("Standard"); + var print_format_list = ["Standard"]; + var default_print_format = locals.DocType[doctype].default_print_format; var print_formats = wn.model.get("Print Format", {doc_type: doctype}) .sort(function(a, b) { return (a > b) ? 1 : -1; }); @@ -114,6 +109,12 @@ $.extend(wn.meta, { if(!in_list(print_format_list, d.name)) print_format_list.push(d.name); }); + + if(default_print_format && default_print_format != "Standard") { + var index = print_format_list.indexOf(default_print_format) - 1; + print_format_list.sort().splice(index, 1); + print_format_list.unshift(default_print_format); + } return print_format_list; }, diff --git a/webnotes/public/js/wn/ui/editor.js b/webnotes/public/js/wn/ui/editor.js index c1d6d705b5..e2574f125b 100644 --- a/webnotes/public/js/wn/ui/editor.js +++ b/webnotes/public/js/wn/ui/editor.js @@ -125,7 +125,7 @@ bsEditor = Class.extend({ clean_html: function() { var html = this.editor.html() || ""; - if(!strip(this.editor.text())) html = ""; + if(!strip(this.editor.text()) && !(this.editor.find("img"))) html = ""; // html = html.replace(/(
|\s|

<\/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 += """
Unsubscribe from this list.
""" % (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