@@ -269,7 +269,7 @@ def get_request_header(key, default=None): | |||||
return request.headers.get(key, default) | return request.headers.get(key, default) | ||||
def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", | def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", | ||||
as_markdown=False, bulk=False, ref_doctype=None, ref_docname=None, | |||||
as_markdown=False, bulk=False, reference_doctype=None, reference_name=None, | |||||
unsubscribe_url=False, attachments=None, content=None, doctype=None, name=None, reply_to=None): | unsubscribe_url=False, attachments=None, content=None, doctype=None, name=None, reply_to=None): | ||||
"""Send email using user's default **Email Account** or global default **Email Account**. | """Send email using user's default **Email Account** or global default **Email Account**. | ||||
@@ -280,8 +280,8 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message | |||||
:param message: (or `content`) Email Content. | :param message: (or `content`) Email Content. | ||||
:param as_markdown: Convert content markdown to HTML. | :param as_markdown: Convert content markdown to HTML. | ||||
:param bulk: Send via scheduled email sender **Bulk Email**. Don't send immediately. | :param bulk: Send via scheduled email sender **Bulk Email**. Don't send immediately. | ||||
:param ref_doctype: (or `doctype`) Append as communication to this DocType. | |||||
:param ref_docname: (or `name`) Append as communication to this document name. | |||||
:param reference_doctype: (or `doctype`) Append as communication to this DocType. | |||||
:param reference_name: (or `name`) Append as communication to this document name. | |||||
:param unsubscribe_url: Unsubscribe url with options email, doctype, name. e.g. `/api/method/unsubscribe?email={email}&name={name}` | :param unsubscribe_url: Unsubscribe url with options email, doctype, name. e.g. `/api/method/unsubscribe?email={email}&name={name}` | ||||
:param attachments: List of attachments. | :param attachments: List of attachments. | ||||
:param reply_to: Reply-To email id. | :param reply_to: Reply-To email id. | ||||
@@ -290,8 +290,8 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message | |||||
if bulk: | if bulk: | ||||
import frappe.email.bulk | import frappe.email.bulk | ||||
frappe.email.bulk.send(recipients=recipients, sender=sender, | frappe.email.bulk.send(recipients=recipients, sender=sender, | ||||
subject=subject, message=content or message, ref_doctype = doctype or ref_doctype, | |||||
ref_docname = name or ref_docname, unsubscribe_url=unsubscribe_url, attachments=attachments, | |||||
subject=subject, message=content or message, reference_doctype = doctype or reference_doctype, | |||||
reference_name = name or reference_name, unsubscribe_url=unsubscribe_url, attachments=attachments, | |||||
reply_to=reply_to) | reply_to=reply_to) | ||||
else: | else: | ||||
@@ -861,7 +861,7 @@ def add_version(doc): | |||||
A **Version** is a JSON dump of the current document state.""" | A **Version** is a JSON dump of the current document state.""" | ||||
get_doc({ | get_doc({ | ||||
"doctype": "Version", | "doctype": "Version", | ||||
"ref_doctype": doc.doctype, | |||||
"reference_doctype": doc.doctype, | |||||
"docname": doc.name, | "docname": doc.name, | ||||
"doclist_json": as_json(doc.as_dict()) | "doclist_json": as_json(doc.as_dict()) | ||||
}).insert(ignore_permissions=True) | }).insert(ignore_permissions=True) | ||||
@@ -90,8 +90,8 @@ class Communication(Document): | |||||
"sender": mail.sender, | "sender": mail.sender, | ||||
"recipient": mail.recipients[0], | "recipient": mail.recipients[0], | ||||
"message": mail.as_string(), | "message": mail.as_string(), | ||||
"ref_doctype": self.reference_doctype, | |||||
"ref_docname": self.reference_name | |||||
"reference_doctype": self.reference_doctype, | |||||
"reference_name": self.reference_name | |||||
}).insert(ignore_permissions=True) | }).insert(ignore_permissions=True) | ||||
def notify(self, mail, except_sender=False): | def notify(self, mail, except_sender=False): | ||||
@@ -4,75 +4,107 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
import HTMLParser | import HTMLParser | ||||
from urllib import quote_plus | |||||
from frappe import msgprint, throw, _ | from frappe import msgprint, throw, _ | ||||
from frappe.email.smtp import SMTPServer, get_outgoing_email_account | from frappe.email.smtp import SMTPServer, get_outgoing_email_account | ||||
from frappe.email.email_body import get_email, get_formatted_html | from frappe.email.email_body import get_email, get_formatted_html | ||||
from frappe.utils.verified_command import get_signed_params, verify_request | |||||
from html2text import html2text | from html2text import html2text | ||||
from frappe.utils import get_url, nowdate | from frappe.utils import get_url, nowdate | ||||
class BulkLimitCrossedError(frappe.ValidationError): pass | class BulkLimitCrossedError(frappe.ValidationError): pass | ||||
def send(recipients=None, sender=None, doctype='User', email_field='email', | |||||
subject='[No Subject]', message='[No Content]', ref_doctype=None, | |||||
ref_docname=None, unsubscribe_url=True, attachments=None, reply_to=None): | |||||
def send(recipients=None, sender=None, subject=None, message=None, reference_doctype=None, | |||||
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, | |||||
attachments=None, reply_to=None, footer_message=None): | |||||
"""Add email to sending queue (Bulk Email) | |||||
if not unsubscribe_url: | |||||
unsubscribe_url = "/api/method/frappe.email.bulk.unsubscribe?doctype={doctype}&name={name}&email={email}" | |||||
:param recipients: List of recipients. | |||||
:param sender: Email sender. | |||||
:param subject: Email subject. | |||||
:param message: Email message. | |||||
:param reference_doctype: Reference DocType of caller document. | |||||
:param reference_name: Reference name of caller document. | |||||
:param unsubscribe_method: URL method for unsubscribe. Default is `/api/method/frappe.email.bulk.unsubscribe`. | |||||
:param unsubscribe_params: additional params for unsubscribed links. default are name, doctype, email | |||||
:param attachments: Attachments to be sent. | |||||
:param reply_to: Reply to be captured here (default inbox)""" | |||||
def check_bulk_limit(new_mails): | |||||
this_month = frappe.db.sql("""select count(*) from `tabBulk Email` where | |||||
month(creation)=month(%s)""" % nowdate())[0][0] | |||||
# No limit for own email settings | |||||
smtp_server = SMTPServer() | |||||
if smtp_server.email_account and not getattr(smtp_server.email_account, | |||||
"from_site_config", False) or frappe.flags.in_test: | |||||
monthly_bulk_mail_limit = frappe.conf.get('monthly_bulk_mail_limit') or 500 | |||||
if (this_month + len(recipients)) > monthly_bulk_mail_limit: | |||||
throw(_("Bulk email limit {0} crossed").format(monthly_bulk_mail_limit), | |||||
BulkLimitCrossedError) | |||||
def update_message(formatted, unsubscribe_url, email): | |||||
updated = formatted | |||||
my_unsubscribe_url = unsubscribe_url.format(email=quote_plus(email), doctype=quote_plus(ref_doctype), | |||||
name=quote_plus(ref_docname)) | |||||
unsubscribe_link = """<div style="padding: 7px; border-top: 1px solid #aaa; margin-top: 17px;"> | |||||
<small><a href="{base_url}/{url}">{message}</a></small></div>""".format(base_url = get_url(), | |||||
url = my_unsubscribe_url, message = _("Unsubscribe from this list")) | |||||
updated = updated.replace("<!--unsubscribe link here-->", unsubscribe_link) | |||||
return updated | |||||
if not unsubscribe_method: | |||||
unsubscribe_method = "/api/method/frappe.email.bulk.unsubscribe" | |||||
if not recipients: | if not recipients: | ||||
recipients = [] | |||||
return | |||||
if not sender or sender == "Administrator": | if not sender or sender == "Administrator": | ||||
email_account = get_outgoing_email_account() | email_account = get_outgoing_email_account() | ||||
sender = email_account.get("sender") or email_account.email_id | sender = email_account.get("sender") or email_account.email_id | ||||
check_bulk_limit(len(recipients)) | |||||
check_bulk_limit(recipients) | |||||
formatted = get_formatted_html(subject, message) | formatted = get_formatted_html(subject, message) | ||||
try: | |||||
text_content = html2text(formatted) | |||||
except HTMLParser.HTMLParseError: | |||||
text_content = "See html attachment" | |||||
unsubscribed = [d.email for d in frappe.db.get_all("Email Unsubscribe", "email", | unsubscribed = [d.email for d in frappe.db.get_all("Email Unsubscribe", "email", | ||||
{"reference_doctype": ref_doctype, "reference_name": ref_docname})] | |||||
{"reference_doctype": reference_doctype, "reference_name": reference_name})] | |||||
for email in filter(None, list(set(recipients))): | |||||
if email not in unsubscribed: | |||||
unsubscribe_url = get_unsubcribed_url(reference_doctype, reference_name, email, | |||||
unsubscribe_method, unsubscribe_params) | |||||
for r in filter(None, list(set(recipients))): | |||||
if r not in unsubscribed: | |||||
# add to queue | # add to queue | ||||
updated = update_message(formatted, unsubscribe_url) | |||||
try: | |||||
text_content = html2text(updated) | |||||
except HTMLParser.HTMLParseError: | |||||
text_content = "[See html attachment]" | |||||
updated = add_unsubscribe_link(formatted, email, reference_doctype, reference_name, | |||||
unsubscribe_url, footer_message) | |||||
text_content += "\n" + _("Unsubscribe link: {0}").format(unsubscribe_url) | |||||
add(email, sender, subject, updated, text_content, reference_doctype, reference_name, attachments, reply_to) | |||||
add(r, sender, subject, updated, text_content, ref_doctype, ref_docname, attachments, reply_to) | |||||
def check_bulk_limit(recipients): | |||||
this_month = frappe.db.sql("""select count(*) from `tabBulk Email` where | |||||
month(creation)=month(%s)""" % nowdate())[0][0] | |||||
# No limit for own email settings | |||||
smtp_server = SMTPServer() | |||||
if smtp_server.email_account and not getattr(smtp_server.email_account, | |||||
"from_site_config", False) or frappe.flags.in_test: | |||||
monthly_bulk_mail_limit = frappe.conf.get('monthly_bulk_mail_limit') or 500 | |||||
if (this_month + len(recipients)) > monthly_bulk_mail_limit: | |||||
throw(_("Bulk email limit {0} crossed").format(monthly_bulk_mail_limit), | |||||
BulkLimitCrossedError) | |||||
def add_unsubscribe_link(message, email, reference_doctype, reference_name, unsubscribe_url, footer_message): | |||||
unsubscribe_link = """<div style="padding: 7px; border-top: 1px solid #aaa; margin-top: 17px;"> | |||||
<small>{footer_message} | |||||
<a href="{unsubscribe_url}">{unsubscribe_message}</a></small></div>""".format(unsubscribe_url = unsubscribe_url, | |||||
unsubscribe_message = _("Unsubscribe from this list"), footer_message= footer_message or "") | |||||
message = message.replace("<!--unsubscribe link here-->", unsubscribe_link) | |||||
return message | |||||
def get_unsubcribed_url(reference_doctype, reference_name, email, unsubscribe_method, unsubscribe_params): | |||||
params = {"email": email.encode("utf-8"), | |||||
"doctype": reference_doctype.encode("utf-8"), | |||||
"name": reference_name.encode("utf-8")} | |||||
if unsubscribe_params: | |||||
params.update(unsubscribe_params) | |||||
query_string = get_signed_params(params) | |||||
# for test | |||||
frappe.local.flags.signed_query_string = query_string | |||||
return get_url(unsubscribe_method + "?" + get_signed_params(params)) | |||||
def add(email, sender, subject, formatted, text_content=None, | def add(email, sender, subject, formatted, text_content=None, | ||||
ref_doctype=None, ref_docname=None, attachments=None, reply_to=None): | |||||
reference_doctype=None, reference_name=None, attachments=None, reply_to=None): | |||||
"""add to bulk mail queue""" | """add to bulk mail queue""" | ||||
e = frappe.new_doc('Bulk Email') | e = frappe.new_doc('Bulk Email') | ||||
e.sender = sender | e.sender = sender | ||||
@@ -85,27 +117,29 @@ def add(email, sender, subject, formatted, text_content=None, | |||||
# bad email id - don't add to queue | # bad email id - don't add to queue | ||||
return | return | ||||
e.ref_doctype = ref_doctype | |||||
e.ref_docname = ref_docname | |||||
e.reference_doctype = reference_doctype | |||||
e.reference_name = reference_name | |||||
e.insert(ignore_permissions=True) | e.insert(ignore_permissions=True) | ||||
@frappe.whitelist(allow_guest=True) | @frappe.whitelist(allow_guest=True) | ||||
def unsubscribe(doctype, name, email): | def unsubscribe(doctype, name, email): | ||||
# unsubsribe from comments and communications | # unsubsribe from comments and communications | ||||
frappe.g | |||||
if not verify_request(): | |||||
return | |||||
frappe.get_doc({ | |||||
"doctype": "Email Unsubscribe", | |||||
"email": email, | |||||
"reference_doctype": doctype, | |||||
"reference_name": name | |||||
}).insert(ignore_permissions=True) | |||||
if not frappe.form_dict.get("from_test"): | |||||
frappe.db.commit() | |||||
frappe.db.commit() | |||||
return_unsubscribed_page(email) | return_unsubscribed_page(email) | ||||
def return_unsubscribed_page(email): | def return_unsubscribed_page(email): | ||||
frappe.local.message_title = _("Unsubscribed") | |||||
frappe.local.message = "<h3>" + _("Unsubscribed") + "</h3><p>" \ | |||||
+ _("{0} has been successfully unsubscribed").fomrat(email) + "</p>" | |||||
frappe.response['type'] = 'page' | |||||
frappe.response['page_name'] = 'message.html' | |||||
frappe.respond_as_web_page(_("Unsubscribed"), _("{0} has been successfully unsubscribed").format(email)) | |||||
def flush(from_test=False): | def flush(from_test=False): | ||||
"""flush email queue, every time: called from scheduler""" | """flush email queue, every time: called from scheduler""" | ||||
@@ -44,7 +44,7 @@ | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "ref_doctype", | |||||
"fieldname": "reference_doctype", | |||||
"fieldtype": "Link", | "fieldtype": "Link", | ||||
"label": "Reference DocType", | "label": "Reference DocType", | ||||
"options": "DocType", | "options": "DocType", | ||||
@@ -53,7 +53,7 @@ | |||||
"reqd": 0 | "reqd": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "ref_docname", | |||||
"fieldname": "reference_name", | |||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"label": "Reference DocName", | "label": "Reference DocName", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
@@ -64,7 +64,7 @@ | |||||
"icon": "icon-envelope", | "icon": "icon-envelope", | ||||
"idx": 1, | "idx": 1, | ||||
"in_create": 1, | "in_create": 1, | ||||
"modified": "2015-01-23 04:32:39.175147", | |||||
"modified": "2015-03-19 05:36:16.813340", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Email", | "module": "Email", | ||||
"name": "Bulk Email", | "name": "Bulk Email", | ||||
@@ -0,0 +1,12 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
import unittest | |||||
# test_records = frappe.get_test_records('Bulk Email') | |||||
class TestBulkEmail(unittest.TestCase): | |||||
pass |
@@ -91,5 +91,5 @@ def evaluate_alert(doc, alert, event): | |||||
frappe.sendmail(recipients=recipients, subject=alert.subject, | frappe.sendmail(recipients=recipients, subject=alert.subject, | ||||
message= frappe.render_template(alert.message, {"doc": doc, "alert":alert}), | message= frappe.render_template(alert.message, {"doc": doc, "alert":alert}), | ||||
bulk=True, ref_doctype = doc.doctype, ref_docname = doc.name, | |||||
bulk=True, reference_doctype = doc.doctype, reference_name = doc.name, | |||||
attachments = [frappe.attach_print(doc.doctype, doc.name)] if alert.attach_print else None) | attachments = [frappe.attach_print(doc.doctype, doc.name)] if alert.attach_print else None) |
@@ -20,16 +20,16 @@ class TestEmailAlert(unittest.TestCase): | |||||
comment.comment = "test" | comment.comment = "test" | ||||
comment.insert(ignore_permissions=True) | comment.insert(ignore_permissions=True) | ||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Comment", | |||||
"ref_docname": comment.name, "status":"Not Sent"})) | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Comment", | |||||
"reference_name": comment.name, "status":"Not Sent"})) | |||||
frappe.db.sql("""delete from `tabBulk Email`""") | frappe.db.sql("""delete from `tabBulk Email`""") | ||||
comment.description = "test" | comment.description = "test" | ||||
comment.save() | comment.save() | ||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Comment", | |||||
"ref_docname": comment.name, "status":"Not Sent"})) | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Comment", | |||||
"reference_name": comment.name, "status":"Not Sent"})) | |||||
def test_condition(self): | def test_condition(self): | ||||
event = frappe.new_doc("Event") | event = frappe.new_doc("Event") | ||||
@@ -38,14 +38,14 @@ class TestEmailAlert(unittest.TestCase): | |||||
event.starts_on = "2014-06-06 12:00:00" | event.starts_on = "2014-06-06 12:00:00" | ||||
event.insert() | event.insert() | ||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event", | |||||
"reference_name": event.name, "status":"Not Sent"})) | |||||
event.event_type = "Public" | event.event_type = "Public" | ||||
event.save() | event.save() | ||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event", | |||||
"reference_name": event.name, "status":"Not Sent"})) | |||||
def test_value_changed(self): | def test_value_changed(self): | ||||
event = frappe.new_doc("Event") | event = frappe.new_doc("Event") | ||||
@@ -54,20 +54,20 @@ class TestEmailAlert(unittest.TestCase): | |||||
event.starts_on = "2014-06-06 12:00:00" | event.starts_on = "2014-06-06 12:00:00" | ||||
event.insert() | event.insert() | ||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event", | |||||
"reference_name": event.name, "status":"Not Sent"})) | |||||
event.subject = "test 1" | event.subject = "test 1" | ||||
event.save() | event.save() | ||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event", | |||||
"reference_name": event.name, "status":"Not Sent"})) | |||||
event.description = "test" | event.description = "test" | ||||
event.save() | event.save() | ||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event", | |||||
"reference_name": event.name, "status":"Not Sent"})) | |||||
def test_date_changed(self): | def test_date_changed(self): | ||||
event = frappe.new_doc("Event") | event = frappe.new_doc("Event") | ||||
@@ -76,23 +76,23 @@ class TestEmailAlert(unittest.TestCase): | |||||
event.starts_on = "2014-01-01 12:00:00" | event.starts_on = "2014-01-01 12:00:00" | ||||
event.insert() | event.insert() | ||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event", | |||||
"reference_name": event.name, "status":"Not Sent"})) | |||||
frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True) | frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True) | ||||
# not today, so no alert | # not today, so no alert | ||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event", | |||||
"reference_name": event.name, "status":"Not Sent"})) | |||||
event.starts_on = frappe.utils.add_days(frappe.utils.nowdate(), 2) + " 12:00:00" | event.starts_on = frappe.utils.add_days(frappe.utils.nowdate(), 2) + " 12:00:00" | ||||
event.save() | event.save() | ||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event", | |||||
"reference_name": event.name, "status":"Not Sent"})) | |||||
frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True) | frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True) | ||||
# today so show alert | # today so show alert | ||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Event", | |||||
"reference_name": event.name, "status":"Not Sent"})) |
@@ -241,7 +241,7 @@ class BaseDocument(object): | |||||
values = ", ".join(["%s"] * len(columns)) | values = ", ".join(["%s"] * len(columns)) | ||||
), d.values()) | ), d.values()) | ||||
except Exception, e: | except Exception, e: | ||||
if e.args[0]==1062 and "PRIMARY" in e.message: | |||||
if e.args[0]==1062 and "PRIMARY" in e.args[1]: | |||||
if self.meta.autoname=="hash": | if self.meta.autoname=="hash": | ||||
self.name = None | self.name = None | ||||
self.db_insert() | self.db_insert() | ||||
@@ -74,9 +74,9 @@ def get_default_value(df, defaults, user_permissions, parent_doc): | |||||
# default value based on another document | # default value based on another document | ||||
ref_doctype = df.default[1:] | ref_doctype = df.default[1:] | ||||
ref_fieldname = ref_doctype.lower().replace(" ", "_") | ref_fieldname = ref_doctype.lower().replace(" ", "_") | ||||
ref_docname = parent_doc.get(ref_fieldname) if parent_doc else frappe.db.get_default(ref_fieldname) | |||||
reference_name = parent_doc.get(ref_fieldname) if parent_doc else frappe.db.get_default(ref_fieldname) | |||||
default_value = frappe.db.get_value(ref_doctype, ref_docname, df.fieldname) | |||||
default_value = frappe.db.get_value(ref_doctype, reference_name, df.fieldname) | |||||
is_allowed_default_value = (not user_permissions_exist or | is_allowed_default_value = (not user_permissions_exist or | ||||
(default_value in user_permissions.get(df.options, []))) | (default_value in user_permissions.get(df.options, []))) | ||||
@@ -65,3 +65,4 @@ frappe.patches.v5_0.fix_feed | |||||
frappe.patches.v5_0.update_shared | frappe.patches.v5_0.update_shared | ||||
frappe.patches.v5_0.bookmarks_to_stars | frappe.patches.v5_0.bookmarks_to_stars | ||||
frappe.patches.v5_0.style_settings_to_website_theme | frappe.patches.v5_0.style_settings_to_website_theme | ||||
frappe.patches.v5_0.rename_ref_type_fieldnames |
@@ -0,0 +1,10 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
def execute(): | |||||
frappe.db.sql("alter table `tabBulk Email` change `ref_docname` `reference_name` varchar(255)") | |||||
frappe.db.sql("alter table `tabBulk Email` change `reference_doctype` `reference_doctype` varchar(255)") | |||||
frappe.reload_doctype("Bulk Email") |
@@ -1,3 +1,7 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | import frappe | ||||
def execute(): | def execute(): | ||||
@@ -53,11 +53,9 @@ def add_comment(args=None): | |||||
from frappe.email.bulk import send | from frappe.email.bulk import send | ||||
send(recipients=recipients, | send(recipients=recipients, | ||||
doctype='Comment', | |||||
email_field='comment_by', | |||||
subject = _("New comment on {0} {1}").format(comment.comment_doctype, comment.comment_docname), | subject = _("New comment on {0} {1}").format(comment.comment_doctype, comment.comment_docname), | ||||
message = message, | message = message, | ||||
ref_doctype=comment.comment_doctype, ref_docname=comment.comment_docname) | |||||
reference_doctype=comment.comment_doctype, reference_name=comment.comment_docname) | |||||
template = frappe.get_template("templates/includes/comments/comment.html") | template = frappe.get_template("templates/includes/comments/comment.html") | ||||
@@ -11,7 +11,7 @@ make_test_records("Email Account") | |||||
class TestEmail(unittest.TestCase): | class TestEmail(unittest.TestCase): | ||||
def setUp(self): | def setUp(self): | ||||
frappe.db.sql("""update tabUser set unsubscribed=0""") | |||||
frappe.db.sql("""delete from `tabEmail Unsubscribe`""") | |||||
frappe.db.sql("""delete from `tabBulk Email`""") | frappe.db.sql("""delete from `tabBulk Email`""") | ||||
def test_send(self): | def test_send(self): | ||||
@@ -22,7 +22,7 @@ class TestEmail(unittest.TestCase): | |||||
from frappe.email.bulk import send | from frappe.email.bulk import send | ||||
send(recipients = ['test@example.com', 'test1@example.com'], | send(recipients = ['test@example.com', 'test1@example.com'], | ||||
sender="admin@example.com", | sender="admin@example.com", | ||||
doctype='User', email_field='email', | |||||
reference_doctype='User', reference_name='Administrator', | |||||
subject='Testing Bulk', message='This is a bulk mail!') | subject='Testing Bulk', message='This is a bulk mail!') | ||||
bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Not Sent'""", as_dict=1) | bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Not Sent'""", as_dict=1) | ||||
@@ -42,17 +42,14 @@ class TestEmail(unittest.TestCase): | |||||
def test_unsubscribe(self): | def test_unsubscribe(self): | ||||
from frappe.email.bulk import unsubscribe, send | from frappe.email.bulk import unsubscribe, send | ||||
frappe.local.form_dict = frappe._dict({ | |||||
'email':'test@example.com', | |||||
'type':'User', | |||||
'email_field':'email', | |||||
"from_test": True | |||||
}) | |||||
unsubscribe() | |||||
unsubscribe(doctype="User", name="Administrator", email="test@example.com") | |||||
self.assertTrue(frappe.db.get_value("Email Unsubscribe", | |||||
{"reference_doctype": "User", "reference_name": "Administrator", "email": "test@example.com"})) | |||||
send(recipients = ['test@example.com', 'test1@example.com'], | send(recipients = ['test@example.com', 'test1@example.com'], | ||||
sender="admin@example.com", | sender="admin@example.com", | ||||
doctype='User', email_field='email', | |||||
reference_doctype='User', reference_name= "Administrator", | |||||
subject='Testing Bulk', message='This is a bulk mail!') | subject='Testing Bulk', message='This is a bulk mail!') | ||||
bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Not Sent'""", | bulk = frappe.db.sql("""select * from `tabBulk Email` where status='Not Sent'""", | ||||
@@ -67,7 +64,7 @@ class TestEmail(unittest.TestCase): | |||||
self.assertRaises(BulkLimitCrossedError, send, | self.assertRaises(BulkLimitCrossedError, send, | ||||
recipients=['test@example.com']*1000, | recipients=['test@example.com']*1000, | ||||
sender="admin@example.com", | sender="admin@example.com", | ||||
doctype='User', email_field='email', | |||||
reference_doctype = "User", reference_name="Administrator", | |||||
subject='Testing Bulk', message='This is a bulk mail!') | subject='Testing Bulk', message='This is a bulk mail!') | ||||
@@ -4,25 +4,40 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import hmac | import hmac | ||||
import urllib | import urllib | ||||
from frappe import _ | |||||
import frappe | import frappe | ||||
import frappe.utils | import frappe.utils | ||||
def get_signed_params(params): | def get_signed_params(params): | ||||
"""Sign a url by appending `&_signature=xxxxx` to given params (string or dict). | |||||
:param params: String or dict of parameters.""" | |||||
if not isinstance(params, basestring): | if not isinstance(params, basestring): | ||||
params = urllib.urlencode(params) | params = urllib.urlencode(params) | ||||
signature = hmac.new(params) | signature = hmac.new(params) | ||||
signature.update(get_secret()) | signature.update(get_secret()) | ||||
return params + "&_signature=" + signature.hexdigest() | return params + "&_signature=" + signature.hexdigest() | ||||
def get_secret(): | def get_secret(): | ||||
return frappe.local.conf.get("secret") or frappe.db.get_value("User", "Administrator", "creation") | |||||
return frappe.local.conf.get("secret") or str(frappe.db.get_value("User", "Administrator", "creation")) | |||||
def verify_request(): | def verify_request(): | ||||
params, signature = frappe.request.query_string.split("&_signature=") | |||||
given_signature = hmac.new(params) | |||||
"""Verify if the incoming signed request if it is correct.""" | |||||
query_string = frappe.request.query_string if hasattr(frappe.request, "query_string") \ | |||||
else frappe.local.flags.signed_query_string | |||||
params, signature = query_string.split("&_signature=") | |||||
given_signature = hmac.new(params.encode("utf-8")) | |||||
given_signature.update(get_secret()) | given_signature.update(get_secret()) | ||||
return signature == given_signature | |||||
valid = signature == given_signature.hexdigest() | |||||
if not valid: | |||||
frappe.respond_as_web_page(_("Invalid Link"), | |||||
_("This link is invalid or expired. Please make sure you have pasted correctly.")) | |||||
return valid | |||||
def get_url(cmd, params, nonce=None, secret=None): | def get_url(cmd, params, nonce=None, secret=None): | ||||
if not nonce: | if not nonce: | ||||