@@ -137,6 +137,21 @@ def get_data(): | |||||
"name": "Standard Reply", | "name": "Standard Reply", | ||||
"description": _("Standard replies to common queries.") | "description": _("Standard replies to common queries.") | ||||
}, | }, | ||||
{ | |||||
"type": "doctype", | |||||
"name": "Newsletter", | |||||
"description": _("Newsletters to contacts, leads."), | |||||
}, | |||||
{ | |||||
"type": "doctype", | |||||
"name": "Email Group", | |||||
"description": _("Email Group List"), | |||||
}, | |||||
{ | |||||
"type": "doctype", | |||||
"name": "Email Group Member", | |||||
"description": _("Email Group Member List"), | |||||
}, | |||||
] | ] | ||||
}, | }, | ||||
{ | { | ||||
@@ -0,0 +1,49 @@ | |||||
// Copyright (c) 2016, Frappe Technologies and contributors | |||||
// For license information, please see license.txt | |||||
frappe.ui.form.on("Email Group", "refresh", function(frm) { | |||||
if(!frm.is_new()) { | |||||
frm.add_custom_button(__("View Subscribers"), function() { | |||||
frappe.route_options = {"email_group": frm.doc.name}; | |||||
frappe.set_route("Report", "Email Group Member"); | |||||
}, __("View")); | |||||
frm.add_custom_button(__("Import Subscribers"), function() { | |||||
frappe.prompt({fieldtype:"Select", options: frm.doc.__onload.import_types, | |||||
label:__("Import Email From"), fieldname:"doctype", reqd:1}, function(data) { | |||||
frappe.call({ | |||||
method: "frappe.email.doctype.email_group.email_group.import_from", | |||||
args: { | |||||
"name": frm.doc.name, | |||||
"doctype": data.doctype | |||||
}, | |||||
callback: function(r) { | |||||
frm.set_value("total_subscribers", r.message); | |||||
} | |||||
}) | |||||
}, __("Import Subscribers"), __("Import")); | |||||
}, __("Action")); | |||||
frm.add_custom_button(__("Add Subscribers"), function() { | |||||
frappe.prompt({fieldtype:"Text", | |||||
label:__("Email Ids"), fieldname:"email_list", reqd:1}, function(data) { | |||||
frappe.call({ | |||||
method: "frappe.email.doctype.email_group.email_group.add_subscribers", | |||||
args: { | |||||
"name": frm.doc.name, | |||||
"email_list": data.email_list | |||||
}, | |||||
callback: function(r) { | |||||
frm.set_value("total_subscribers", r.message); | |||||
} | |||||
}) | |||||
}, __("Add Subscribers"), __("Add")); | |||||
}, __("Action")); | |||||
frm.add_custom_button(__("New Newsletter"), function() { | |||||
frappe.route_options = {"email_group": frm.doc.name}; | |||||
frappe.new_doc("Newsletter"); | |||||
}, __("Action")); | |||||
} | |||||
}); |
@@ -0,0 +1,109 @@ | |||||
{ | |||||
"allow_copy": 0, | |||||
"allow_import": 1, | |||||
"allow_rename": 0, | |||||
"autoname": "field:title", | |||||
"beta": 0, | |||||
"creation": "2015-03-18 06:08:32.729800", | |||||
"custom": 0, | |||||
"docstatus": 0, | |||||
"doctype": "DocType", | |||||
"document_type": "Setup", | |||||
"fields": [ | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "title", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Title", | |||||
"length": 0, | |||||
"no_copy": 1, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"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, | |||||
"default": "0", | |||||
"fieldname": "total_subscribers", | |||||
"fieldtype": "Int", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "Total Subscribers", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
} | |||||
], | |||||
"hide_heading": 0, | |||||
"hide_toolbar": 0, | |||||
"idx": 0, | |||||
"image_view": 0, | |||||
"in_create": 0, | |||||
"in_dialog": 0, | |||||
"is_submittable": 0, | |||||
"issingle": 0, | |||||
"istable": 0, | |||||
"max_attachments": 0, | |||||
"modified": "2016-06-28 15:33:58.274566", | |||||
"modified_by": "Administrator", | |||||
"module": "Email", | |||||
"name": "Email Group", | |||||
"name_case": "", | |||||
"owner": "Administrator", | |||||
"permissions": [ | |||||
{ | |||||
"amend": 0, | |||||
"apply_user_permissions": 0, | |||||
"cancel": 0, | |||||
"create": 1, | |||||
"delete": 1, | |||||
"email": 1, | |||||
"export": 1, | |||||
"if_owner": 0, | |||||
"import": 1, | |||||
"permlevel": 0, | |||||
"print": 1, | |||||
"read": 1, | |||||
"report": 1, | |||||
"role": "Newsletter Manager", | |||||
"set_user_permissions": 0, | |||||
"share": 1, | |||||
"submit": 0, | |||||
"write": 1 | |||||
} | |||||
], | |||||
"quick_entry": 0, | |||||
"read_only": 0, | |||||
"read_only_onload": 0, | |||||
"sort_field": "modified", | |||||
"sort_order": "DESC", | |||||
"track_seen": 0 | |||||
} |
@@ -0,0 +1,99 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (c) 2015, Frappe Technologies and contributors | |||||
# For license information, please see license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
from frappe.model.document import Document | |||||
class EmailGroup(Document): | |||||
def onload(self): | |||||
singles = [d.name for d in frappe.db.get_all("DocType", "name", {"issingle": 1})] | |||||
self.get("__onload").import_types = [{"value": d.parent, "label": "{0} ({1})".format(d.parent, d.label)} \ | |||||
for d in frappe.db.get_all("DocField", ("parent", "label"), {"options": "Email"}) | |||||
if d.parent not in singles] | |||||
def import_from(self, doctype): | |||||
"""Extract email ids from given doctype and add them to the current list""" | |||||
meta = frappe.get_meta(doctype) | |||||
email_field = [d.fieldname for d in meta.fields | |||||
if d.fieldtype in ("Data", "Small Text", "Text", "Code") and d.options=="Email"][0] | |||||
unsubscribed_field = "unsubscribed" if meta.get_field("unsubscribed") else None | |||||
added = 0 | |||||
for user in frappe.db.get_all(doctype, [email_field, unsubscribed_field or "name"]): | |||||
try: | |||||
email = parseaddr(user.get(email_field))[1] | |||||
if email: | |||||
frappe.get_doc({ | |||||
"doctype": "Email Group Member", | |||||
"email_group": self.name, | |||||
"email": email, | |||||
"unsubscribed": user.get(unsubscribed_field) if unsubscribed_field else 0 | |||||
}).insert(ignore_permissions=True) | |||||
added += 1 | |||||
except frappe.UniqueValidationError: | |||||
pass | |||||
frappe.msgprint(_("{0} subscribers added").format(added)) | |||||
return self.update_total_subscribers() | |||||
def update_total_subscribers(self): | |||||
self.total_subscribers = self.get_total_subscribers() | |||||
self.db_update() | |||||
return self.total_subscribers | |||||
def get_total_subscribers(self): | |||||
return frappe.db.sql("""select count(*) from `tabEmail Group Member` | |||||
where email_group=%s""", self.name)[0][0] | |||||
def on_trash(self): | |||||
for d in frappe.get_all("Email Group Member", "name", {"email_group": self.name}): | |||||
frappe.delete_doc("Email Group Member", d.name) | |||||
@frappe.whitelist() | |||||
def import_from(name, doctype): | |||||
nlist = frappe.get_doc("Email Group", name) | |||||
if nlist.has_permission("write"): | |||||
return nlist.import_from(doctype) | |||||
@frappe.whitelist() | |||||
def add_subscribers(name, email_list): | |||||
if not isinstance(email_list, (list, tuple)): | |||||
email_list = email_list.replace(",", "\n").split("\n") | |||||
count = 0 | |||||
for email in email_list: | |||||
email = email.strip() | |||||
valid = validate_email_add(email, False) | |||||
if valid: | |||||
if not frappe.db.get_value("Email Group Member", | |||||
{"email_group": name, "email": email}): | |||||
frappe.get_doc({ | |||||
"doctype": "Email Group Member", | |||||
"email_group": name, | |||||
"email": email | |||||
}).insert(ignore_permissions = frappe.flags.ignore_permissions) | |||||
count += 1 | |||||
else: | |||||
pass | |||||
else: | |||||
frappe.msgprint(_("{0} is not a valid email id").format(email)) | |||||
frappe.msgprint(_("{0} subscribers added").format(count)) | |||||
return frappe.get_doc("Email Group", name).update_total_subscribers() | |||||
def restrict_email_group(doc, method): | |||||
from frappe.limits import get_limits | |||||
email_group_limit = get_limits().get('newsletter_recipients') | |||||
if not email_group_limit: | |||||
return | |||||
nl = frappe.get_doc("Email Group", doc.email_group) | |||||
if nl.get_total_subscribers() >= email_group_limit: | |||||
frappe.throw(_("Please Upgrade to add more than {0} subscribers").format(email_group_limit)) |
@@ -0,0 +1,12 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (c) 2015, Frappe Technologies and Contributors | |||||
# See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
import unittest | |||||
# test_records = frappe.get_test_records('Email Group') | |||||
class TestEmailGroup(unittest.TestCase): | |||||
pass |
@@ -0,0 +1,6 @@ | |||||
[ | |||||
{ | |||||
"doctype": "Email Group", | |||||
"title": "_Test Email Group" | |||||
} | |||||
] |
@@ -0,0 +1,8 @@ | |||||
// Copyright (c) 2016, Frappe Technologies and contributors | |||||
// For license information, please see license.txt | |||||
frappe.ui.form.on('Email Group Member', { | |||||
refresh: function(frm) { | |||||
} | |||||
}); |
@@ -0,0 +1,135 @@ | |||||
{ | |||||
"allow_copy": 0, | |||||
"allow_import": 1, | |||||
"allow_rename": 0, | |||||
"autoname": "hash", | |||||
"beta": 0, | |||||
"creation": "2015-03-18 06:15:59.321619", | |||||
"custom": 0, | |||||
"docstatus": 0, | |||||
"doctype": "DocType", | |||||
"document_type": "Document", | |||||
"fields": [ | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "email_group", | |||||
"fieldtype": "Link", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "Email Group", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "Email Group", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"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, | |||||
"fieldname": "email", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "Email", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"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, | |||||
"fieldname": "unsubscribed", | |||||
"fieldtype": "Check", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "Unsubscribed", | |||||
"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, | |||||
"hide_toolbar": 0, | |||||
"idx": 0, | |||||
"image_view": 0, | |||||
"in_create": 0, | |||||
"in_dialog": 0, | |||||
"is_submittable": 0, | |||||
"issingle": 0, | |||||
"istable": 0, | |||||
"max_attachments": 0, | |||||
"modified": "2016-06-28 17:00:27.546534", | |||||
"modified_by": "Administrator", | |||||
"module": "Email", | |||||
"name": "Email Group Member", | |||||
"name_case": "", | |||||
"owner": "Administrator", | |||||
"permissions": [ | |||||
{ | |||||
"amend": 0, | |||||
"apply_user_permissions": 0, | |||||
"cancel": 0, | |||||
"create": 1, | |||||
"delete": 1, | |||||
"email": 1, | |||||
"export": 1, | |||||
"if_owner": 0, | |||||
"import": 1, | |||||
"permlevel": 0, | |||||
"print": 1, | |||||
"read": 1, | |||||
"report": 1, | |||||
"role": "Newsletter Manager", | |||||
"set_user_permissions": 0, | |||||
"share": 1, | |||||
"submit": 0, | |||||
"write": 1 | |||||
} | |||||
], | |||||
"quick_entry": 0, | |||||
"read_only": 0, | |||||
"read_only_onload": 0, | |||||
"sort_field": "modified", | |||||
"sort_order": "DESC", | |||||
"title_field": "email", | |||||
"track_seen": 0 | |||||
} |
@@ -0,0 +1,13 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (c) 2015, Frappe Technologies and contributors | |||||
# For license information, please see license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
from frappe.model.document import Document | |||||
class EmailGroupMember(Document): | |||||
pass | |||||
def after_doctype_insert(): | |||||
frappe.db.add_unique("Email Group Member", ("email_group", "email")) |
@@ -0,0 +1,12 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (c) 2015, Frappe Technologies and Contributors | |||||
# See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
import unittest | |||||
# test_records = frappe.get_test_records('Email Group Member') | |||||
class TestEmailGroupMember(unittest.TestCase): | |||||
pass |
@@ -0,0 +1,63 @@ | |||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
// License: GNU General Public License v3. See license.txt | |||||
cur_frm.cscript.onload = function(doc) { | |||||
return frappe.call({ | |||||
method: "frappe.email.doctype.newsletter.newsletter.get_lead_options", | |||||
type: "GET", | |||||
callback: function(r) { | |||||
set_field_options("lead_source", r.message.sources.join("\n")) | |||||
set_field_options("lead_status", r.message.statuses.join("\n")) | |||||
} | |||||
}); | |||||
} | |||||
cur_frm.cscript.refresh = function(doc) { | |||||
erpnext.toggle_naming_series(); | |||||
if(!doc.__islocal && !cint(doc.email_sent) && !doc.__unsaved | |||||
&& inList(frappe.boot.user.can_write, doc.doctype)) { | |||||
cur_frm.add_custom_button(__('Send'), function() { | |||||
return $c_obj(doc, 'send_emails', '', function(r) { | |||||
cur_frm.refresh(); | |||||
}); | |||||
}, "icon-play", "btn-success"); | |||||
} | |||||
cur_frm.cscript.setup_dashboard(); | |||||
if(doc.__islocal && !doc.send_from) { | |||||
cur_frm.set_value("send_from", | |||||
repl("%(fullname)s <%(email)s>", frappe.user_info(doc.owner))); | |||||
} | |||||
} | |||||
cur_frm.cscript.setup_dashboard = function() { | |||||
cur_frm.dashboard.reset(); | |||||
if(!cur_frm.doc.__islocal && cint(cur_frm.doc.email_sent) && cur_frm.doc.__onload && cur_frm.doc.__onload.status_count) { | |||||
var stat = cur_frm.doc.__onload.status_count; | |||||
var total = frappe.utils.sum($.map(stat, function(v) { return v; })); | |||||
if(total) { | |||||
$.each(stat, function(k, v) { | |||||
stat[k] = flt(v * 100 / total, 2) + '%'; | |||||
}); | |||||
cur_frm.dashboard.add_progress("Status", [ | |||||
{ | |||||
title: stat["Sent"] + "% Sent", | |||||
width: stat["Sent"], | |||||
progress_class: "progress-bar-success" | |||||
}, | |||||
{ | |||||
title: stat["Sending"] + "% Sending", | |||||
width: stat["Sending"], | |||||
progress_class: "progress-bar-warning" | |||||
}, | |||||
{ | |||||
title: stat["Error"] + "% Error", | |||||
width: stat["Error"], | |||||
progress_class: "progress-bar-danger" | |||||
} | |||||
]); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,302 @@ | |||||
{ | |||||
"allow_copy": 0, | |||||
"allow_import": 0, | |||||
"allow_rename": 1, | |||||
"autoname": "field:subject", | |||||
"beta": 0, | |||||
"creation": "2013-01-10 16:34:31", | |||||
"custom": 0, | |||||
"description": "Create and Send Newsletters", | |||||
"docstatus": 0, | |||||
"doctype": "DocType", | |||||
"document_type": "Other", | |||||
"fields": [ | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "email_group", | |||||
"fieldtype": "Link", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Email Group", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "Email Group", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"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, | |||||
"fieldname": "subject", | |||||
"fieldtype": "Small Text", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Subject", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"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": "", | |||||
"fieldname": "send_from", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 1, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Sender", | |||||
"length": 0, | |||||
"no_copy": 1, | |||||
"permlevel": 0, | |||||
"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, | |||||
"collapsible": 0, | |||||
"fieldname": "email_sent", | |||||
"fieldtype": "Check", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Email Sent?", | |||||
"length": 0, | |||||
"no_copy": 1, | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "newsletter_content", | |||||
"fieldtype": "Section Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"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, | |||||
"collapsible": 0, | |||||
"fieldname": "message", | |||||
"fieldtype": "Text Editor", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Message", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"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": "", | |||||
"fieldname": "test_the_newsletter", | |||||
"fieldtype": "Section Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"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, | |||||
"collapsible": 0, | |||||
"description": "A Lead with this email id should exist", | |||||
"fieldname": "test_email_id", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Test Email Id", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"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, | |||||
"collapsible": 0, | |||||
"fieldname": "test_send", | |||||
"fieldtype": "Button", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Test", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "test_send", | |||||
"permlevel": 0, | |||||
"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, | |||||
"hide_toolbar": 0, | |||||
"icon": "icon-envelope", | |||||
"idx": 1, | |||||
"image_view": 0, | |||||
"in_create": 0, | |||||
"in_dialog": 0, | |||||
"is_submittable": 0, | |||||
"issingle": 0, | |||||
"istable": 0, | |||||
"max_attachments": 0, | |||||
"menu_index": 0, | |||||
"modified": "2016-06-28 17:20:07.227578", | |||||
"modified_by": "Administrator", | |||||
"module": "Email", | |||||
"name": "Newsletter", | |||||
"owner": "Administrator", | |||||
"permissions": [ | |||||
{ | |||||
"amend": 0, | |||||
"apply_user_permissions": 1, | |||||
"cancel": 0, | |||||
"create": 1, | |||||
"delete": 1, | |||||
"email": 1, | |||||
"export": 1, | |||||
"if_owner": 0, | |||||
"import": 0, | |||||
"permlevel": 0, | |||||
"print": 1, | |||||
"read": 1, | |||||
"report": 1, | |||||
"role": "All", | |||||
"set_user_permissions": 0, | |||||
"share": 1, | |||||
"submit": 0, | |||||
"write": 1 | |||||
}, | |||||
{ | |||||
"amend": 0, | |||||
"apply_user_permissions": 1, | |||||
"cancel": 0, | |||||
"create": 0, | |||||
"delete": 1, | |||||
"email": 1, | |||||
"export": 1, | |||||
"if_owner": 0, | |||||
"import": 0, | |||||
"permlevel": 0, | |||||
"print": 1, | |||||
"read": 1, | |||||
"report": 1, | |||||
"role": "Newsletter Manager", | |||||
"set_user_permissions": 0, | |||||
"share": 1, | |||||
"submit": 0, | |||||
"write": 0 | |||||
} | |||||
], | |||||
"quick_entry": 1, | |||||
"read_only": 0, | |||||
"read_only_onload": 0, | |||||
"sort_order": "ASC", | |||||
"title_field": "subject", | |||||
"track_seen": 0 | |||||
} |
@@ -0,0 +1,189 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# License: GNU General Public License v3. See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
import frappe.utils | |||||
from frappe import throw, _ | |||||
from frappe.model.document import Document | |||||
from frappe.email.queue import check_email_limit | |||||
from frappe.utils.verified_command import get_signed_params, verify_request | |||||
from frappe.utils.background_jobs import enqueue | |||||
from frappe.utils.scheduler import log | |||||
from frappe.email.queue import send | |||||
from frappe.email.doctype.email_group.email_group import add_subscribers | |||||
class Newsletter(Document): | |||||
def onload(self): | |||||
if self.email_sent: | |||||
self.get("__onload").status_count = dict(frappe.db.sql("""select status, count(name) | |||||
from `tabEmail Queue` where reference_doctype=%s and reference_name=%s | |||||
group by status""", (self.doctype, self.name))) or None | |||||
def test_send(self, doctype="Lead"): | |||||
self.recipients = frappe.utils.split_emails(self.test_email_id) | |||||
self.queue_all() | |||||
frappe.msgprint(_("Scheduled to send to {0}").format(self.test_email_id)) | |||||
def send_emails(self): | |||||
"""send emails to leads and customers""" | |||||
if self.email_sent: | |||||
throw(_("Newsletter has already been sent")) | |||||
self.recipients = self.get_recipients() | |||||
if getattr(frappe.local, "is_ajax", False): | |||||
self.validate_send() | |||||
# using default queue with a longer timeout as this isn't a scheduled task | |||||
enqueue(send_newsletter, queue='default', timeout=1500, event='send_newsletter', newsletter=self.name) | |||||
else: | |||||
self.queue_all() | |||||
frappe.msgprint(_("Scheduled to send to {0} recipients").format(len(self.recipients))) | |||||
frappe.db.set(self, "email_sent", 1) | |||||
def queue_all(self): | |||||
if not self.get("recipients"): | |||||
# in case it is called via worker | |||||
self.recipients = self.get_recipients() | |||||
self.validate_send() | |||||
sender = self.send_from or frappe.utils.get_formatted_email(self.owner) | |||||
if not frappe.flags.in_test: | |||||
frappe.db.auto_commit_on_many_writes = True | |||||
send(recipients = self.recipients, sender = sender, | |||||
subject = self.subject, message = self.message, | |||||
reference_doctype = self.doctype, reference_name = self.name, | |||||
unsubscribe_method = "/api/method/frappe.email.doctype.newsletter.newsletter.unsubscribe", | |||||
unsubscribe_params = {"name": self.email_group}, | |||||
send_priority = 0) | |||||
if not frappe.flags.in_test: | |||||
frappe.db.auto_commit_on_many_writes = False | |||||
def get_recipients(self): | |||||
"""Get recipients from Email Group""" | |||||
return [d.email for d in frappe.db.get_all("Email Group Member", ["email"], | |||||
{"unsubscribed": 0, "email_group": self.email_group})] | |||||
def validate_send(self): | |||||
if self.get("__islocal"): | |||||
throw(_("Please save the Newsletter before sending")) | |||||
check_email_limit(self.recipients) | |||||
@frappe.whitelist() | |||||
def get_lead_options(): | |||||
return { | |||||
"sources": ["All"] + filter(None, | |||||
frappe.db.sql_list("""select distinct source from tabLead""")), | |||||
"statuses": ["All"] + filter(None, | |||||
frappe.db.sql_list("""select distinct status from tabLead""")) | |||||
} | |||||
@frappe.whitelist(allow_guest=True) | |||||
def unsubscribe(email, name): | |||||
if not verify_request(): | |||||
return | |||||
subs_id = frappe.db.get_value("Email Group Member", {"email": email, "email_group": name}) | |||||
if subs_id: | |||||
subscriber = frappe.get_doc("Email Group Member", subs_id) | |||||
subscriber.unsubscribed = 1 | |||||
subscriber.save(ignore_permissions=True) | |||||
frappe.db.commit() | |||||
return_unsubscribed_page(email) | |||||
def return_unsubscribed_page(email): | |||||
frappe.respond_as_web_page(_("Unsubscribed"), _("{0} has been successfully unsubscribed from this list.").format(email)) | |||||
def create_lead(email_id): | |||||
"""create a lead if it does not exist""" | |||||
from email.utils import parseaddr | |||||
from frappe.model.naming import get_default_naming_series | |||||
real_name, email_id = parseaddr(email_id) | |||||
if frappe.db.get_value("Lead", {"email_id": email_id}): | |||||
return | |||||
lead = frappe.get_doc({ | |||||
"doctype": "Lead", | |||||
"email_id": email_id, | |||||
"lead_name": real_name or email_id, | |||||
"status": "Lead", | |||||
"naming_series": get_default_naming_series("Lead"), | |||||
"company": frappe.db.get_default("Company"), | |||||
"source": "Email" | |||||
}) | |||||
lead.insert() | |||||
@frappe.whitelist(allow_guest=True) | |||||
def subscribe(email): | |||||
url = frappe.utils.get_url("/api/method/frappe.email.doctype.newsletter.newsletter.confirm_subscription") +\ | |||||
"?" + get_signed_params({"email": email}) | |||||
messages = ( | |||||
_("Thank you for your interest in subscribing to our updates"), | |||||
_("Please verify your email id"), | |||||
url, | |||||
_("Click here to verify") | |||||
) | |||||
content = """ | |||||
<p>{0}. {1}.</p> | |||||
<p><a href="{2}">{3}</a></p> | |||||
""" | |||||
frappe.sendmail(email, subject=_("Confirm Your Email"), content=content.format(*messages)) | |||||
@frappe.whitelist(allow_guest=True) | |||||
def confirm_subscription(email): | |||||
if not verify_request(): | |||||
return | |||||
if not frappe.db.exists("Email Group", _("Website")): | |||||
frappe.get_doc({ | |||||
"doctype": "Email Group", | |||||
"title": _("Website") | |||||
}).insert(ignore_permissions=True) | |||||
frappe.flags.ignore_permissions = True | |||||
add_subscribers(_("Website"), email) | |||||
frappe.db.commit() | |||||
frappe.respond_as_web_page(_("Confirmed"), _("{0} has been successfully added to our Email Group.").format(email)) | |||||
def send_newsletter(newsletter): | |||||
try: | |||||
doc = frappe.get_doc("Newsletter", newsletter) | |||||
doc.queue_all() | |||||
except: | |||||
frappe.db.rollback() | |||||
# wasn't able to send emails :( | |||||
doc.db_set("email_sent", 0) | |||||
frappe.db.commit() | |||||
log("send_newsletter") | |||||
raise | |||||
else: | |||||
frappe.db.commit() | |||||
@@ -0,0 +1,10 @@ | |||||
frappe.listview_settings['Newsletter'] = { | |||||
add_fields: ["subject", "email_sent"], | |||||
get_indicator: function(doc) { | |||||
if(doc.email_sent) { | |||||
return [__("Sent"), "green", "email_sent,=,Yes"]; | |||||
} else { | |||||
return [__("Not Sent"), "orange", "email_sent,=,No"]; | |||||
} | |||||
} | |||||
}; |
@@ -0,0 +1,49 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# License: GNU General Public License v3. See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe, unittest | |||||
from frappe.email.doctype.newsletter.newsletter import unsubscribe | |||||
from urllib import unquote | |||||
class TestNewsletter(unittest.TestCase): | |||||
def setUp(self): | |||||
frappe.db.sql('delete from `tabEmail Group Member`') | |||||
for email in ["test_subscriber1@example.com", "test_subscriber2@example.com", | |||||
"test_subscriber3@example.com"]: | |||||
frappe.get_doc({ | |||||
"doctype": "Email Group Member", | |||||
"email": email, | |||||
"email_group": "_Test Email Group" | |||||
}).insert() | |||||
def test_send(self): | |||||
self.send_newsletter() | |||||
self.assertEquals(len(frappe.get_all("Email Queue")), 3) | |||||
def test_unsubscribe(self): | |||||
# test unsubscribe | |||||
self.send_newsletter() | |||||
email = unquote(frappe.local.flags.signed_query_string.split("email=")[1].split("&")[0]) | |||||
unsubscribe(email, "_Test Email Group") | |||||
self.send_newsletter() | |||||
self.assertEquals(len(frappe.get_all("Email Queue")), 2) | |||||
def send_newsletter(self): | |||||
frappe.db.sql("delete from `tabEmail Queue`") | |||||
frappe.delete_doc("Newsletter", "_Test Newsletter") | |||||
newsletter = frappe.get_doc({ | |||||
"doctype": "Newsletter", | |||||
"subject": "_Test Newsletter", | |||||
"email_group": "_Test Email Group", | |||||
"send_from": "Test Sender <test_sender@example.com>", | |||||
"message": "Testing my news." | |||||
}).insert(ignore_permissions=True) | |||||
newsletter.send_emails() | |||||
test_dependencies = ["Email Group"] |
@@ -97,7 +97,10 @@ standard_queries = { | |||||
doc_events = { | doc_events = { | ||||
"*": { | "*": { | ||||
"after_insert": "frappe.email.doctype.email_alert.email_alert.trigger_email_alerts", | "after_insert": "frappe.email.doctype.email_alert.email_alert.trigger_email_alerts", | ||||
"validate": "frappe.email.doctype.email_alert.email_alert.trigger_email_alerts", | |||||
"validate": [ | |||||
"frappe.email.doctype.email_alert.email_alert.trigger_email_alerts", | |||||
"frappe.email.doctype.email_group.email_group.restrict_email_group" | |||||
], | |||||
"on_update": [ | "on_update": [ | ||||
"frappe.desk.notifications.clear_doctype_notifications", | "frappe.desk.notifications.clear_doctype_notifications", | ||||
"frappe.email.doctype.email_alert.email_alert.trigger_email_alerts", | "frappe.email.doctype.email_alert.email_alert.trigger_email_alerts", | ||||
@@ -132,3 +132,4 @@ frappe.patches.v7_0.setup_list_settings | |||||
execute:frappe.db.sql('''delete from `tabSingles` where doctype="Email Settings"''') # 2016-06-13 | execute:frappe.db.sql('''delete from `tabSingles` where doctype="Email Settings"''') # 2016-06-13 | ||||
execute:frappe.db.sql("delete from `tabWeb Page` where ifnull(template_path, '')!=''") | execute:frappe.db.sql("delete from `tabWeb Page` where ifnull(template_path, '')!=''") | ||||
frappe.patches.v7_0.re_route #2016-06-27 | frappe.patches.v7_0.re_route #2016-06-27 | ||||
frappe.patches.v7_0.rename_newsletter_list_to_email_group |
@@ -0,0 +1,5 @@ | |||||
import frappe | |||||
def execute(): | |||||
frappe.rename_doc('DocType', 'Newsletter List', 'Email Group') | |||||
frappe.rename_doc('DocType', 'Newsletter List Subscriber', 'Email Group Member') |