Merge branch 'master' into develop

This commit is contained in:
Anand Doshi 2016-04-20 16:30:31 +05:30
commit 4bf0e8fce7
23 changed files with 1234 additions and 1031 deletions

View file

@ -338,7 +338,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, content=None, doctype=None, name=None, reply_to=None,
cc=(), show_as_cc=(), message_id=None, in_reply_to=None, as_bulk=False, send_after=None, expose_recipients=False,
bulk_priority=1):
bulk_priority=1, communication=None):
"""Send email using user's default **Email Account** or global default **Email Account**.
@ -359,6 +359,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
:param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To.
:param send_after: Send after the given datetime.
:param expose_recipients: Display all recipients in the footer message - "This email was sent to"
:param communication: Communication link to be set in Bulk Email record
"""
if bulk or as_bulk:
@ -368,7 +369,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message
reference_doctype = doctype or reference_doctype, reference_name = name or reference_name,
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message,
attachments=attachments, reply_to=reply_to, cc=cc, show_as_cc=show_as_cc, message_id=message_id, in_reply_to=in_reply_to,
send_after=send_after, expose_recipients=expose_recipients, bulk_priority=bulk_priority)
send_after=send_after, expose_recipients=expose_recipients, bulk_priority=bulk_priority, communication=communication)
else:
import frappe.email
if as_markdown:

View file

@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = "6.27.10"
__version__ = "6.27.11"

View file

@ -0,0 +1,2 @@
- Get [email sending status](https://discuss.erpnext.com/t/communication-delivery-status-bulk-email-status/11941) in document timeline
- Ability to disable a Role

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,8 @@ from frappe.core.doctype.communication.comment import (validate_comment,
from frappe.core.doctype.communication.email import (validate_email,
notify, _notify, update_parent_status)
from frappe.utils.bot import BotReply
from email.utils import parseaddr
from collections import Counter
exclude_from_linked_with = True
@ -107,7 +109,16 @@ class Communication(Document):
self.sender = None
else:
validate_email_add(self.sender, throw=True)
self.sender_full_name = get_fullname(self.sender)
sender_name, sender_email = parseaddr(self.sender)
if not sender_name:
sender_name = get_fullname(sender_email)
if sender_name == sender_email:
sender_name = None
self.sender = sender_email
self.sender_full_name = sender_name or get_fullname(frappe.session.user)
def get_parent_doc(self):
"""Returns document of `reference_doctype`, `reference_doctype`"""
@ -181,6 +192,34 @@ class Communication(Document):
}).insert()
frappe.local.flags.commit = True
def set_delivery_status(self, commit=False):
'''Look into the status of Bulk Email linked to this Communication and set the Delivery Status of this Communication'''
delivery_status = None
status_counts = Counter(frappe.db.sql_list('''select status from `tabBulk Email` where communication=%s''', self.name))
if status_counts.get('Not Sent') or status_counts.get('Sending'):
delivery_status = 'Sending'
elif status_counts.get('Error'):
delivery_status = 'Error'
elif status_counts.get('Expired'):
delivery_status = 'Expired'
elif status_counts.get('Sent'):
delivery_status = 'Sent'
if delivery_status:
self.db_set('delivery_status', delivery_status)
frappe.publish_realtime('update_communication', self.as_dict(),
doctype=self.reference_doctype, docname=self.reference_name, after_commit=True)
# for list views and forms
self.notify_update()
if commit:
frappe.db.commit()
def on_doctype_update():
"""Add index in `tabCommunication` for `(reference_doctype, reference_name)`"""

View file

@ -5,7 +5,7 @@ from __future__ import unicode_literals, absolute_import
import frappe
import json
from email.utils import formataddr, parseaddr
from frappe.utils import get_url, get_formatted_email, cint, validate_email_add, split_emails
from frappe.utils import get_url, get_formatted_email, cint, validate_email_add, split_emails, get_fullname
from frappe.utils.file_manager import get_file
from frappe.email.bulk import check_bulk_limit
from frappe.utils.scheduler import log
@ -18,8 +18,7 @@ from frappe.utils.background_jobs import enqueue
@frappe.whitelist()
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
sender=None, recipients=None, communication_medium="Email", send_email=False,
print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False,
send_me_a_copy=False, cc=None):
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, flags=None):
"""Make a new communication.
:param doctype: Reference DocType.
@ -38,8 +37,9 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
"""
is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report")
send_me_a_copy = cint(send_me_a_copy)
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not ignore_doctype_permissions:
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'):
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
doctype=doctype, name=name))
@ -63,8 +63,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
# if not committed, delayed task doesn't find the communication
frappe.db.commit()
if send_email:
comm.send_me_a_copy = send_me_a_copy
if cint(send_email):
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy)
return {
@ -85,6 +84,8 @@ def validate_email(doc):
for email in split_emails(doc.cc):
validate_email_add(email, throw=True)
# validate sender
def notify(doc, print_html=None, print_format=None, attachments=None,
recipients=None, cc=None, fetched_from_email_account=False):
"""Calls a delayed task 'sendmail' that enqueus email in Bulk Email queue
@ -131,7 +132,8 @@ def _notify(doc, print_html=None, print_format=None, attachments=None,
attachments=doc.attachments,
message_id=doc.name,
unsubscribe_message=_("Leave this conversation"),
bulk=True
bulk=True,
communication=doc.name
)
def update_parent_status(doc):
@ -191,12 +193,15 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None)
set_incoming_outgoing_accounts(doc)
if not doc.sender or cint(doc.outgoing_email_account.always_use_account_email_id_as_sender):
sender_name = (frappe.session.data.full_name
or doc.outgoing_email_account.name
or _("Notification"))
sender_email_id = doc.outgoing_email_account.email_id
doc.sender = formataddr([sender_name, sender_email_id])
if not doc.sender:
doc.sender = doc.outgoing_email_account.email_id
if not doc.sender_full_name:
doc.sender_full_name = doc.outgoing_email_account.name or _("Notification")
if doc.sender:
# combine for sending to get the format 'Jane <jane@example.com>'
doc.sender = formataddr([doc.sender_full_name, doc.sender])
doc.attachments = []

View file

@ -16,6 +16,7 @@
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Role Name",
@ -25,12 +26,39 @@
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "If disabled, this role will be removed from all users.",
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Disabled",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@ -43,7 +71,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2015-11-16 06:29:55.424835",
"modified": "2016-04-20 12:54:08.406706",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",
@ -110,6 +138,9 @@
"write": 0
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0
"read_only_onload": 0,
"sort_order": "ASC",
"track_seen": 0
}

View file

@ -17,3 +17,11 @@ class Role(Document):
user = frappe.get_doc("User", "Administrator")
user.flags.ignore_permissions = True
user.add_roles(self.name)
def validate(self):
if self.disabled:
if self.name in ("Guest", "Administrator", "System Manager", "All"):
frappe.throw(frappe._("Standard roles cannot be disabled"))
else:
frappe.db.sql("delete from `tabUserRole` where role = %s", self.name)
frappe.clear_cache()

View file

@ -6,5 +6,9 @@
{
"doctype": "Role",
"role_name": "_Test Role 2"
},
{
"doctype": "Role",
"role_name": "_Test Role 3"
}
]

View file

@ -3,5 +3,27 @@
from __future__ import unicode_literals
import frappe
import unittest
test_records = frappe.get_test_records('Role')
test_records = frappe.get_test_records('Role')
class TestUser(unittest.TestCase):
def test_disable_role(self):
frappe.get_doc("User", "test@example.com").add_roles("_Test Role 3")
role = frappe.get_doc("Role", "_Test Role 3")
role.disabled = 1
role.save()
self.assertTrue("_Test Role 3" not in frappe.get_roles("test@example.com"))
frappe.get_doc("User", "test@example.com").add_roles("_Test Role 3")
self.assertTrue("_Test Role 3" not in frappe.get_roles("test@example.com"))
role = frappe.get_doc("Role", "_Test Role 3")
role.disabled = 0
role.save()
frappe.get_doc("User", "test@example.com").add_roles("_Test Role 3")
self.assertTrue("_Test Role 3" in frappe.get_roles("test@example.com"))

View file

@ -3,7 +3,7 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import cint, has_gravatar, format_datetime, now_datetime
from frappe.utils import cint, get_gravatar, format_datetime, now_datetime, get_formatted_email
from frappe import throw, msgprint, _
from frappe.auth import _update_password
from frappe.desk.notifications import clear_notifications
@ -46,6 +46,7 @@ class User(Document):
self.ensure_unique_roles()
self.remove_all_roles_for_guest()
self.validate_username()
self.remove_disabled_roles()
if self.language == "Loading...":
self.language = None
@ -77,7 +78,7 @@ class User(Document):
"doctype": "UserRole",
"role": "System Manager"
})
if self.name == 'Administrator':
# Administrator should always have System Manager Role
self.extend("user_roles", [
@ -88,7 +89,7 @@ class User(Document):
{
"doctype": "UserRole",
"role": "Administrator"
}
}
])
def email_new_password(self, new_password=None):
@ -213,7 +214,7 @@ class User(Document):
args.update(add_args)
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
sender = frappe.session.user not in STANDARD_USERS and get_formatted_email(frappe.session.user) or None
frappe.sendmail(recipients=self.email, sender=sender, subject=subject,
message=frappe.get_template(template).render(args), as_bulk=self.flags.delay_emails)
@ -315,6 +316,12 @@ class User(Document):
if self.name == "Guest":
self.set("user_roles", list(set(d for d in self.get("user_roles") if d.role == "Guest")))
def remove_disabled_roles(self):
disabled_roles = [d.name for d in frappe.get_all("Role", filters={"disabled":1})]
for role in list(self.get('user_roles')):
if role.role in disabled_roles:
self.get('user_roles').remove(role)
def ensure_unique_roles(self):
exists = []
for i, d in enumerate(self.get("user_roles")):
@ -382,7 +389,7 @@ def get_timezones():
def get_all_roles(arg=None):
"""return all roles"""
return [r[0] for r in frappe.db.sql("""select name from tabRole
where name not in ('Administrator', 'Guest', 'All') order by name""")]
where name not in ('Administrator', 'Guest', 'All') and not disabled order by name""")]
@frappe.whitelist()
def get_user_roles(arg=None):

View file

@ -1,2 +0,0 @@
gems:
- jekyll-redirect-from

View file

@ -1,6 +1,3 @@
---
redirect_from: "/user/tutorial"
---
# Frappe Tutorial
In this guide we will show you how to create an application from scratch using **Frappe**. Using the example of a Library Management System, we will cover:

View file

@ -17,7 +17,7 @@ class BulkLimitCrossedError(frappe.ValidationError): pass
def send(recipients=None, sender=None, subject=None, message=None, reference_doctype=None,
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, reply_to=None, cc=(), show_as_cc=(), message_id=None, in_reply_to=None, send_after=None,
expose_recipients=False, bulk_priority=1):
expose_recipients=False, bulk_priority=1, communication=None):
"""Add email to sending queue (Bulk Email)
:param recipients: List of recipients.
@ -34,6 +34,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
:param message_id: Used for threading. If a reply is received to this email, Message-Id is sent back as In-Reply-To in received email.
:param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To.
:param send_after: Send this email after the given datetime. If value is in integer, then `send_after` will be the automatically set to no of days from current date.
:param communication: Communication link to be set in Bulk Email record
"""
if not unsubscribe_method:
unsubscribe_method = "/api/method/frappe.email.bulk.unsubscribe"
@ -101,19 +102,18 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc
# add to queue
add(email, sender, subject, email_content, email_text_context, reference_doctype,
reference_name, attachments, reply_to, cc, message_id, in_reply_to, send_after, bulk_priority)
reference_name, attachments, reply_to, cc, message_id, in_reply_to, send_after, bulk_priority, email_account=email_account, communication=communication)
def add(email, sender, subject, formatted, text_content=None,
reference_doctype=None, reference_name=None, attachments=None, reply_to=None,
cc=(), message_id=None, in_reply_to=None, send_after=None, bulk_priority=1, email_account=None):
cc=(), message_id=None, in_reply_to=None, send_after=None, bulk_priority=1, email_account=None, communication=None):
"""add to bulk mail queue"""
e = frappe.new_doc('Bulk Email')
e.sender = sender
e.recipient = email
e.priority = bulk_priority
try:
mail = get_email(email, sender=e.sender, formatted=formatted, subject=subject,
mail = get_email(email, sender=sender, formatted=formatted, subject=subject,
text_content=text_content, attachments=attachments, reply_to=reply_to, cc=cc, email_account=email_account)
if message_id:
@ -123,6 +123,7 @@ def add(email, sender, subject, formatted, text_content=None,
mail.set_in_reply_to(in_reply_to)
e.message = cstr(mail.as_string())
e.sender = mail.sender
except frappe.InvalidEmailAddressError:
# bad email id - don't add to queue
@ -130,12 +131,13 @@ def add(email, sender, subject, formatted, text_content=None,
e.reference_doctype = reference_doctype
e.reference_name = reference_name
e.communication = communication
e.send_after = send_after
e.insert(ignore_permissions=True)
def check_bulk_limit(recipients):
# get count of mails sent this month
this_month = frappe.db.sql("""select count(*) from `tabBulk Email` where
this_month = frappe.db.sql("""select count(name) from `tabBulk Email` where
status='Sent' and MONTH(creation)=MONTH(CURDATE())""")[0][0]
# if using settings from site_config.json, check bulk limit
@ -272,6 +274,10 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False):
frappe.db.sql("""update `tabBulk Email` set status='Sending' where name=%s""",
(email.name,), auto_commit=auto_commit)
if email.communication:
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)
try:
if auto_commit:
smtpserver.setup_email_account(email.reference_doctype)
@ -281,6 +287,9 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False):
frappe.db.sql("""update `tabBulk Email` set status='Sent' where name=%s""",
(email.name,), auto_commit=auto_commit)
if email.communication:
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)
except (smtplib.SMTPServerDisconnected,
smtplib.SMTPConnectError,
smtplib.SMTPHeloError,
@ -290,12 +299,19 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False):
frappe.db.sql("""update `tabBulk Email` set status='Not Sent' where name=%s""",
(email.name,), auto_commit=auto_commit)
if email.communication:
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)
# no need to attempt further
return
except Exception, e:
frappe.db.sql("""update `tabBulk Email` set status='Error', error=%s
where name=%s""", (unicode(e), email.name), auto_commit=auto_commit)
if email.communication:
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)
if now:
raise e

View file

@ -183,6 +183,32 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "communication",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Communication",
"length": 0,
"no_copy": 0,
"options": "Communication",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@ -245,8 +271,8 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-02-26 06:44:01.199764",
"modified_by": "anand@erpnext.com",
"modified": "2016-04-18 05:13:07.741981",
"modified_by": "Administrator",
"module": "Email",
"name": "Bulk Email",
"owner": "Administrator",

View file

@ -13,7 +13,7 @@ def get_email(recipients, sender='', msg='', subject='[No Subject]',
content=None, reply_to=None, cc=(), email_account=None):
"""send an html email as multipart with attachments and all"""
content = content or msg
emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=None)
emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account)
if not content.strip().startswith("<"):
content = markdown(content)
@ -57,7 +57,7 @@ class EMail:
self.cc = cc or []
self.html_set = False
self.email_account = email_account
self.email_account = email_account or get_outgoing_email_account()
def set_html(self, message, text_content = None, footer=None, print_html=None, formatted=None):
"""Attach message in the html portion of multipart/alternative"""
@ -156,25 +156,29 @@ class EMail:
def add_pdf_attachment(self, name, html, options=None):
self.add_attachment(name, get_pdf(html, options), 'application/octet-stream')
def get_default_sender(self):
return get_outgoing_email_account().default_sender
def validate(self):
"""validate the email ids"""
from frappe.utils import validate_email_add
if not self.sender:
self.sender = self.get_default_sender()
self.sender = self.email_account.default_sender
validate_email_add(strip(self.sender), True)
self.reply_to = validate_email_add(strip(self.reply_to) or self.sender, True)
self.replace_sender()
self.recipients = [strip(r) for r in self.recipients]
self.cc = [strip(r) for r in self.cc]
for e in self.recipients + (self.cc or []):
validate_email_add(e, True)
def replace_sender(self):
if cint(self.email_account.always_use_account_email_id_as_sender):
sender_name, sender_email = email.utils.parseaddr(self.sender)
self.sender = email.utils.formataddr((sender_name or self.email_account.name, self.email_account.email_id))
def set_message_id(self, message_id):
self.msg_root["Message-Id"] = "<{0}@{1}>".format(message_id, frappe.local.site)

View file

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe
import smtplib
import email.utils
import _socket
import _socket, sys
from frappe.utils import cint
from frappe import _
@ -22,9 +22,11 @@ def send(email, append_to=None):
try:
smtpserver = SMTPServer(append_to=append_to)
smtpserver.replace_sender_in_email(email)
smtpserver.sess.sendmail(email.sender, email.recipients + (email.cc or []),
email.as_string())
# validate is called in as_string
email_body = email.as_string()
smtpserver.sess.sendmail(email.sender, email.recipients + (email.cc or []), email_body)
except smtplib.SMTPSenderRefused:
frappe.msgprint(_("Invalid login or password"))
@ -56,14 +58,25 @@ def get_outgoing_email_account(raise_exception_not_set=True, append_to=None):
frappe.OutgoingEmailError)
if email_account:
email_account.default_sender = email.utils.formataddr((email_account.name,
email_account.get("sender") or email_account.get("email_id")))
email_account.default_sender = email.utils.formataddr((email_account.name, email_account.get("email_id")))
frappe.local.outgoing_email_account[append_to or "default"] = email_account
return frappe.local.outgoing_email_account[append_to or "default"]
def get_default_outgoing_email_account(raise_exception_not_set=True):
'''conf should be like:
{
"mail_server": "smtp.example.com",
"mail_port": 587,
"use_ssl": 1,
"mail_login": "emails@example.com",
"mail_password": "Super.Secret.Password",
"auto_email_id": "emails@example.com",
"email_sender_name": "Example Notifications",
"always_use_account_email_id_as_sender": 0
}
'''
email_account = _get_email_account({"enable_outgoing": 1, "default_outgoing": 1})
if not email_account and frappe.conf.get("mail_server"):
@ -73,9 +86,10 @@ def get_default_outgoing_email_account(raise_exception_not_set=True):
"smtp_server": frappe.conf.get("mail_server"),
"smtp_port": frappe.conf.get("mail_port"),
"use_tls": cint(frappe.conf.get("use_ssl") or 0),
"email_id": frappe.conf.get("mail_login"),
"login_id": frappe.conf.get("mail_login"),
"email_id": frappe.conf.get("auto_email_id") or frappe.conf.get("mail_login") or 'notifications@example.com',
"password": frappe.conf.get("mail_password"),
"sender": frappe.conf.get("auto_email_id", "notifications@example.com")
"always_use_account_email_id_as_sender": frappe.conf.get("always_use_account_email_id_as_sender", 0)
})
email_account.from_site_config = True
email_account.name = frappe.conf.get("email_sender_name") or "Frappe"
@ -87,7 +101,7 @@ def get_default_outgoing_email_account(raise_exception_not_set=True):
# create a stub
email_account = frappe.new_doc("Email Account")
email_account.update({
"sender": "notifications@example.com"
"email_id": "notifications@example.com"
})
return email_account
@ -117,20 +131,12 @@ class SMTPServer:
self.email_account = get_outgoing_email_account(raise_exception_not_set=False, append_to=append_to)
if self.email_account:
self.server = self.email_account.smtp_server
self.login = getattr(self.email_account, "login_id", None) \
or self.email_account.email_id
self.login = getattr(self.email_account, "login_id", None) or 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
self.sender = self.email_account.email_id
self.always_use_account_email_id_as_sender = self.email_account.get("always_use_account_email_id_as_sender")
def replace_sender_in_email(self, email):
if hasattr(self, "always_use_account_email_id_as_sender") and \
cint(self.always_use_account_email_id_as_sender) and self.login:
if not email.reply_to:
email.reply_to = email.sender
email.sender = self.login
self.always_use_account_email_id_as_sender = cint(self.email_account.get("always_use_account_email_id_as_sender"))
@property
def sess(self):
@ -172,11 +178,19 @@ class SMTPServer:
return self._sess
except _socket.error:
except _socket.error, e:
# Invalid mail server -- due to refusing connection
frappe.throw(_('Invalid Outgoing Mail Server or Port'))
except smtplib.SMTPAuthenticationError:
frappe.throw(_("Invalid login or password"))
frappe.msgprint(_('Invalid Outgoing Mail Server or Port'))
type, value, traceback = sys.exc_info()
raise frappe.ValidationError, e, traceback
except smtplib.SMTPAuthenticationError, e:
frappe.msgprint(_("Invalid login or password"))
type, value, traceback = sys.exc_info()
raise frappe.ValidationError, e, traceback
except smtplib.SMTPException:
frappe.msgprint(_('Unable to send emails at this time'))
raise

View file

@ -5,7 +5,7 @@ app_publisher = "Frappe Technologies"
app_description = "Full stack web framework with Python, Javascript, MariaDB, Redis, Node"
app_icon = "octicon octicon-circuit-board"
app_version = "6.27.10"
app_version = "6.27.11"
app_color = "orange"
source_link = "https://github.com/frappe/frappe"
app_license = "MIT"

View file

@ -1,6 +1,8 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.provide('frappe.timeline');
frappe.ui.form.Timeline = Class.extend({
init: function(opts) {
$.extend(this, opts);
@ -556,3 +558,71 @@ frappe.ui.form.Timeline = Class.extend({
frappe.ui.setup_like_popover(this.wrapper, ".comment-likes");
}
});
$.extend(frappe.timeline, {
new_communication: function(communication) {
var docinfo = frappe.model.get_docinfo(communication.reference_doctype, communication.reference_name);
if (docinfo && docinfo.communications) {
var communications = docinfo.communications;
var communication_exists = false;
for (var i=0, l=communications.length; i<l; i++) {
if (communications[i].name==communication.name) {
communication_exists = true;
break;
}
}
if (!communication_exists) {
docinfo.communications = communications.concat([communication]);
}
}
if (cur_frm.doctype === communication.reference_doctype && cur_frm.docname === communication.reference_name) {
cur_frm.timeline && cur_frm.timeline.refresh();
}
},
delete_communication: function(communication) {
var docinfo = frappe.model.get_docinfo(communication.reference_doctype, communication.reference_name);
var index = frappe.timeline.index_of_communication(communication, docinfo);
if (index !== -1) {
// remove it from communications list
docinfo.communications.splice(index, 1);
}
if (cur_frm.doctype === communication.reference_doctype && cur_frm.docname === communication.reference_name) {
cur_frm.timeline && cur_frm.timeline.refresh();
}
},
update_communication: function(communication) {
var docinfo = frappe.model.get_docinfo(communication.reference_doctype, communication.reference_name);
var index = frappe.timeline.index_of_communication(communication, docinfo);
if (index !== -1) {
// update
$.extend(docinfo.communications[index], communication);
}
if (cur_frm.doctype === communication.reference_doctype && cur_frm.docname === communication.reference_name) {
cur_frm.timeline && cur_frm.timeline.refresh();
}
},
index_of_communication: function(communication, docinfo) {
var index = -1;
if (docinfo && docinfo.communications) {
var communications = docinfo.communications;
for (var i=0, l=communications.length; i<l; i++) {
if (communications[i].name==communication.name) {
index = i;
break;
}
}
}
return index;
},
})

View file

@ -40,6 +40,8 @@
{% if (data.delivery_status) {
if (in_list(["Sent", "Opened", "Clicked"], data.delivery_status)) {
var indicator_class = "green";
} else if (data.delivery_status === "Sending") {
var indicator_class = "orange";
} else {
var indicator_class = "red";
}

View file

@ -160,53 +160,6 @@ $.extend(frappe.model, {
}
},
new_communication: function(communication) {
var docinfo = frappe.model.get_docinfo(communication.reference_doctype, communication.reference_name);
if (docinfo && docinfo.communications) {
var communications = docinfo.communications;
var communication_exists = false;
for (var i=0, l=communications.length; i<l; i++) {
if (communications[i].name==communication.name) {
communication_exists = true;
break;
}
}
if (!communication_exists) {
docinfo.communications = communications.concat([communication]);
}
}
if (cur_frm.doctype === communication.reference_doctype && cur_frm.docname === communication.reference_name) {
cur_frm.timeline && cur_frm.timeline.refresh();
}
},
delete_communication: function(communication) {
var docinfo = frappe.model.get_docinfo(communication.reference_doctype, communication.reference_name);
if (docinfo && docinfo.communications) {
var communications = docinfo.communications;
var index = -1;
for (var i=0, l=communications.length; i<l; i++) {
if (communications[i].name==communication.name) {
index = i;
break;
}
}
if (index !== -1) {
// remove it from communications list
docinfo.communications.splice(index, 1);
}
}
if (cur_frm.doctype === communication.reference_doctype && cur_frm.docname === communication.reference_name) {
cur_frm.timeline && cur_frm.timeline.refresh();
}
},
get_shared: function(doctype, name) {
return frappe.model.get_docinfo(doctype, name).shared;
},

View file

@ -26,11 +26,15 @@ frappe.views.FormFactory = frappe.views.Factory.extend({
});
frappe.realtime.on("new_communication", function(data) {
frappe.model.new_communication(data);
frappe.timeline.new_communication(data);
});
frappe.realtime.on("delete_communication", function(data) {
frappe.model.delete_communication(data);
frappe.timeline.delete_communication(data);
});
frappe.realtime.on('update_communication', function(data) {
frappe.timeline.update_communication(data);
});
frappe.realtime.on("doc_viewers", function(data) {

View file

@ -1,7 +1,7 @@
from setuptools import setup, find_packages
from pip.req import parse_requirements
version = "6.27.10"
version = "6.27.11"
requirements = parse_requirements("requirements.txt", session="")
setup(