diff --git a/frappe/config/setup.py b/frappe/config/setup.py index 36d655a3b5..ee83945661 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -137,6 +137,21 @@ def get_data(): "name": "Standard Reply", "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"), + }, ] }, { diff --git a/frappe/email/doctype/email_group/__init__.py b/frappe/email/doctype/email_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/email/doctype/email_group/email_group.js b/frappe/email/doctype/email_group/email_group.js new file mode 100644 index 0000000000..b5702029f1 --- /dev/null +++ b/frappe/email/doctype/email_group/email_group.js @@ -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")); + + } +}); diff --git a/frappe/email/doctype/email_group/email_group.json b/frappe/email/doctype/email_group/email_group.json new file mode 100644 index 0000000000..7a40b5fd71 --- /dev/null +++ b/frappe/email/doctype/email_group/email_group.json @@ -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 +} \ No newline at end of file diff --git a/frappe/email/doctype/email_group/email_group.py b/frappe/email/doctype/email_group/email_group.py new file mode 100644 index 0000000000..c96ad632c2 --- /dev/null +++ b/frappe/email/doctype/email_group/email_group.py @@ -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)) diff --git a/frappe/email/doctype/email_group/test_email_group.py b/frappe/email/doctype/email_group/test_email_group.py new file mode 100644 index 0000000000..09f4f4c32c --- /dev/null +++ b/frappe/email/doctype/email_group/test_email_group.py @@ -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 diff --git a/frappe/email/doctype/email_group/test_records.json b/frappe/email/doctype/email_group/test_records.json new file mode 100644 index 0000000000..a55b117697 --- /dev/null +++ b/frappe/email/doctype/email_group/test_records.json @@ -0,0 +1,6 @@ +[ + { + "doctype": "Email Group", + "title": "_Test Email Group" + } +] diff --git a/frappe/email/doctype/email_group_member/__init__.py b/frappe/email/doctype/email_group_member/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/email/doctype/email_group_member/email_group_member.js b/frappe/email/doctype/email_group_member/email_group_member.js new file mode 100644 index 0000000000..417eb70119 --- /dev/null +++ b/frappe/email/doctype/email_group_member/email_group_member.js @@ -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) { + + } +}); diff --git a/frappe/email/doctype/email_group_member/email_group_member.json b/frappe/email/doctype/email_group_member/email_group_member.json new file mode 100644 index 0000000000..916b06b024 --- /dev/null +++ b/frappe/email/doctype/email_group_member/email_group_member.json @@ -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 +} \ No newline at end of file diff --git a/frappe/email/doctype/email_group_member/email_group_member.py b/frappe/email/doctype/email_group_member/email_group_member.py new file mode 100644 index 0000000000..d0968425d0 --- /dev/null +++ b/frappe/email/doctype/email_group_member/email_group_member.py @@ -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")) \ No newline at end of file diff --git a/frappe/email/doctype/email_group_member/test_email_group_member.py b/frappe/email/doctype/email_group_member/test_email_group_member.py new file mode 100644 index 0000000000..35259617c1 --- /dev/null +++ b/frappe/email/doctype/email_group_member/test_email_group_member.py @@ -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 diff --git a/frappe/email/doctype/newsletter/__init__.py b/frappe/email/doctype/newsletter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js new file mode 100644 index 0000000000..46a8efb043 --- /dev/null +++ b/frappe/email/doctype/newsletter/newsletter.js @@ -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" + } + ]); + } + } +} diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json new file mode 100644 index 0000000000..e77e978046 --- /dev/null +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -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 +} \ No newline at end of file diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py new file mode 100755 index 0000000000..9dcc5f88d2 --- /dev/null +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -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 = """ +
{0}. {1}.
+ + """ + + 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() + + + diff --git a/frappe/email/doctype/newsletter/newsletter_list.js b/frappe/email/doctype/newsletter/newsletter_list.js new file mode 100644 index 0000000000..e95d29545d --- /dev/null +++ b/frappe/email/doctype/newsletter/newsletter_list.js @@ -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"]; + } + } +}; diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py new file mode 100644 index 0000000000..a379be27e1 --- /dev/null +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -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