diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json index 4fd21e8b5e..dce8a9e44f 100644 --- a/frappe/email/doctype/newsletter/newsletter.json +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -15,6 +15,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -44,6 +45,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -74,6 +76,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -103,6 +106,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -131,6 +135,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -159,6 +164,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -170,7 +176,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Subject", "length": 0, @@ -187,6 +193,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -198,7 +205,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Message", "length": 0, @@ -215,6 +222,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -245,6 +253,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -275,6 +284,69 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "description": "", + "fieldname": "published", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Published", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "route", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Route", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -304,6 +376,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -333,6 +406,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -362,6 +436,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -391,19 +466,20 @@ "unique": 0 } ], - "has_web_view": 0, + "has_web_view": 1, "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-envelope", "idx": 1, "image_view": 0, "in_create": 0, + "is_published_field": "published", "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 3, "menu_index": 0, - "modified": "2017-03-07 12:59:18.173824", + "modified": "2017-09-14 15:38:01.891251", "modified_by": "Administrator", "module": "Email", "name": "Newsletter", @@ -433,6 +509,7 @@ "quick_entry": 0, "read_only": 0, "read_only_onload": 0, + "route": "newsletters", "show_name_in_global_search": 0, "sort_order": "ASC", "title_field": "subject", diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 1813b01c53..d69fae1f1d 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe import frappe.utils from frappe import throw, _ -from frappe.model.document import Document +from frappe.website.website_generator import WebsiteGenerator 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 @@ -17,7 +17,7 @@ from frappe.utils import parse_addr from frappe.utils import validate_email_add -class Newsletter(Document): +class Newsletter(WebsiteGenerator): def onload(self): if self.email_sent: self.get("__onload").status_count = dict(frappe.db.sql("""select status, count(name) @@ -25,6 +25,7 @@ class Newsletter(Document): group by status""", (self.doctype, self.name))) or None def validate(self): + self.route = "newsletters/" + self.name if self.send_from: validate_email_add(self.send_from, True) @@ -105,6 +106,26 @@ class Newsletter(Document): throw(_("Please save the Newsletter before sending")) check_email_limit(self.recipients) + def get_context(self, context): + newsletters = get_newsletter_list("Newsletter", None, None, 0) + if newsletters: + newsletter_list = [d.name for d in newsletters] + if self.name not in newsletter_list: + frappe.redirect_to_message(_('Permission Error'), + _("You are not permitted to view the newsletter.")) + frappe.local.flags.redirect_location = frappe.local.response.location + raise frappe.Redirect + else: + context.attachments = get_attachments(self.name) + context.no_cache = 1 + context.show_sidebar = True + + +def get_attachments(name): + return frappe.get_all("File", + fields=["name", "file_name", "file_url", "is_private"], + filters = {"attached_to_name": name, "attached_to_doctype": "Newsletter", "is_private":0}) + def get_email_groups(name): return frappe.db.get_all("Newsletter Email Group", ["email_group"],{"parent":name, "parenttype":"Newsletter"}) @@ -219,4 +240,24 @@ def send_newsletter(newsletter): frappe.db.commit() +def get_list_context(context=None): + context.update({ + "show_sidebar": True, + "show_search": True, + 'no_breadcrumbs': True, + "title": _("Newsletter"), + "get_list": get_newsletter_list, + "row_template": "email/doctype/newsletter/templates/newsletter_row.html", + }) + + +def get_newsletter_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"): + email_group_list = frappe.db.sql('''select eg.name from `tabEmail Group` eg, `tabEmail Group Member` egm + where egm.unsubscribed=0 and eg.name=egm.email_group and egm.email = %s''', frappe.session.user) + if email_group_list: + return frappe.db.sql('''select n.name, n.subject, n.message, n.modified + from `tabNewsletter` n, `tabNewsletter Email Group` neg + where n.name = neg.parent and n.email_sent=1 and n.published=1 and neg.email_group in %s + order by n.modified desc limit {0}, {1} + '''.format(limit_start, limit_page_length), [email_group_list], as_dict=1) diff --git a/frappe/email/doctype/newsletter/templates/newsletter.html b/frappe/email/doctype/newsletter/templates/newsletter.html new file mode 100644 index 0000000000..733c7df6af --- /dev/null +++ b/frappe/email/doctype/newsletter/templates/newsletter.html @@ -0,0 +1,65 @@ +{% extends "templates/web.html" %} + +{% block title %} {{ _("Newsletter") }} {% endblock %} + +{% block page_content %} + + +
+
+
+

{{ doc.subject }}

+

+ {{ frappe.format_date(doc.modified) }} +

+
+
+ {{ doc.message }} +
+
+ + {% if attachments %} +
+
+
+ {{ _("Attachments") }} +
+
+
+
+ {% for attachment in attachments %} +

+ + {{ attachment.file_name }} + +

+ {% endfor %} +
+
+
+ {% endif %} + +
+{% endblock %} \ No newline at end of file diff --git a/frappe/email/doctype/newsletter/templates/newsletter_row.html b/frappe/email/doctype/newsletter/templates/newsletter_row.html new file mode 100644 index 0000000000..503fadb1ea --- /dev/null +++ b/frappe/email/doctype/newsletter/templates/newsletter_row.html @@ -0,0 +1,15 @@ +
+ +
+
+ {{ doc.subject }} +
+
+
+ {{ frappe.utils.pretty_date(doc.modified) }} +
+
+
+
+
\ No newline at end of file diff --git a/frappe/email/doctype/newsletter/test_newsletter.js b/frappe/email/doctype/newsletter/test_newsletter.js new file mode 100644 index 0000000000..40664a439a --- /dev/null +++ b/frappe/email/doctype/newsletter/test_newsletter.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Newsletter", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Newsletter + () => frappe.tests.make('Newsletter', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py index 2b13799775..9b2f275260 100644 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -7,24 +7,26 @@ import frappe, unittest from frappe.email.doctype.newsletter.newsletter import confirmed_unsubscribe from six.moves.urllib.parse import unquote + emails = ["test_subscriber1@example.com", "test_subscriber2@example.com", - "test_subscriber3@example.com"] + "test_subscriber3@example.com", "test1@example.com"] class TestNewsletter(unittest.TestCase): def setUp(self): + frappe.set_user("Administrator") frappe.db.sql('delete from `tabEmail Group Member`') for email in emails: frappe.get_doc({ "doctype": "Email Group Member", "email": email, "email_group": "_Test Email Group" - }).insert() + }).insert() def test_send(self): name = self.send_newsletter() email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")] - self.assertEquals(len(email_queue_list), 3) + self.assertEquals(len(email_queue_list), 4) recipients = [e.recipients[0].recipient for e in email_queue_list] for email in emails: self.assertTrue(email in recipients) @@ -41,13 +43,14 @@ class TestNewsletter(unittest.TestCase): name = self.send_newsletter() email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")] - self.assertEquals(len(email_queue_list), 2) + self.assertEquals(len(email_queue_list), 3) recipients = [e.recipients[0].recipient for e in email_queue_list] for email in emails: if email != to_unsubscribe: self.assertTrue(email in recipients) - def send_newsletter(self): + @staticmethod + def send_newsletter(published=0): frappe.db.sql("delete from `tabEmail Queue`") frappe.db.sql("delete from `tabEmail Queue Recipient`") frappe.db.sql("delete from `tabNewsletter`") @@ -55,7 +58,8 @@ class TestNewsletter(unittest.TestCase): "doctype": "Newsletter", "subject": "_Test Newsletter", "send_from": "Test Sender ", - "message": "Testing my news." + "message": "Testing my news.", + "published": published }).insert(ignore_permissions=True) newsletter.append("email_group", {"email_group": "_Test Email Group"}) @@ -63,4 +67,21 @@ class TestNewsletter(unittest.TestCase): newsletter.send_emails() return newsletter.name + def test_portal(self): + self.send_newsletter(1) + frappe.set_user("test1@example.com") + from frappe.email.doctype.newsletter.newsletter import get_newsletter_list + newsletters = get_newsletter_list("Newsletter", None, None, 0) + self.assertEquals(len(newsletters), 1) + + def test_newsletter_context(self): + context = frappe._dict() + newsletter_name = self.send_newsletter(1) + frappe.set_user("test2@example.com") + doc = frappe.get_doc("Newsletter", newsletter_name) + doc.get_context(context) + self.assertEquals(context.no_cache, 1) + self.assertTrue("attachments" not in context.keys()) + + test_dependencies = ["Email Group"] diff --git a/frappe/hooks.py b/frappe/hooks.py index f5d98f0421..9bdd8352a6 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from . import __version__ as app_version + app_name = "frappe" app_title = "Frappe Framework" app_publisher = "Frappe Technologies" @@ -48,9 +49,11 @@ bootstrap = "assets/frappe/css/bootstrap.css" web_include_css = [ "assets/css/frappe-web.css" ] + website_route_rules = [ {"from_route": "/blog/", "to_route": "Blog Post"}, - {"from_route": "/kb/", "to_route": "Help Article"} + {"from_route": "/kb/", "to_route": "Help Article"}, + {"from_route": "/newsletters", "to_route": "Newsletter"} ] write_file_keys = ["file_url", "file_name"]