Portal for Newsletterversion-14
@@ -15,6 +15,7 @@ | |||||
"engine": "InnoDB", | "engine": "InnoDB", | ||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -44,6 +45,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -74,6 +76,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -103,6 +106,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -131,6 +135,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -159,6 +164,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -170,7 +176,7 @@ | |||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_global_search": 1, | "in_global_search": 1, | ||||
"in_list_view": 0, | |||||
"in_list_view": 1, | |||||
"in_standard_filter": 0, | "in_standard_filter": 0, | ||||
"label": "Subject", | "label": "Subject", | ||||
"length": 0, | "length": 0, | ||||
@@ -187,6 +193,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -198,7 +205,7 @@ | |||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_global_search": 0, | "in_global_search": 0, | ||||
"in_list_view": 0, | |||||
"in_list_view": 1, | |||||
"in_standard_filter": 0, | "in_standard_filter": 0, | ||||
"label": "Message", | "label": "Message", | ||||
"length": 0, | "length": 0, | ||||
@@ -215,6 +222,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -245,6 +253,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -275,6 +284,69 @@ | |||||
"unique": 0 | "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, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -304,6 +376,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -333,6 +406,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -362,6 +436,7 @@ | |||||
"unique": 0 | "unique": 0 | ||||
}, | }, | ||||
{ | { | ||||
"allow_bulk_edit": 0, | |||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
@@ -391,19 +466,20 @@ | |||||
"unique": 0 | "unique": 0 | ||||
} | } | ||||
], | ], | ||||
"has_web_view": 0, | |||||
"has_web_view": 1, | |||||
"hide_heading": 0, | "hide_heading": 0, | ||||
"hide_toolbar": 0, | "hide_toolbar": 0, | ||||
"icon": "fa fa-envelope", | "icon": "fa fa-envelope", | ||||
"idx": 1, | "idx": 1, | ||||
"image_view": 0, | "image_view": 0, | ||||
"in_create": 0, | "in_create": 0, | ||||
"is_published_field": "published", | |||||
"is_submittable": 0, | "is_submittable": 0, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 3, | "max_attachments": 3, | ||||
"menu_index": 0, | "menu_index": 0, | ||||
"modified": "2017-03-07 12:59:18.173824", | |||||
"modified": "2017-09-14 15:38:01.891251", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Email", | "module": "Email", | ||||
"name": "Newsletter", | "name": "Newsletter", | ||||
@@ -433,6 +509,7 @@ | |||||
"quick_entry": 0, | "quick_entry": 0, | ||||
"read_only": 0, | "read_only": 0, | ||||
"read_only_onload": 0, | "read_only_onload": 0, | ||||
"route": "newsletters", | |||||
"show_name_in_global_search": 0, | "show_name_in_global_search": 0, | ||||
"sort_order": "ASC", | "sort_order": "ASC", | ||||
"title_field": "subject", | "title_field": "subject", | ||||
@@ -6,7 +6,7 @@ from __future__ import unicode_literals | |||||
import frappe | import frappe | ||||
import frappe.utils | import frappe.utils | ||||
from frappe import throw, _ | 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.email.queue import check_email_limit | ||||
from frappe.utils.verified_command import get_signed_params, verify_request | from frappe.utils.verified_command import get_signed_params, verify_request | ||||
from frappe.utils.background_jobs import enqueue | from frappe.utils.background_jobs import enqueue | ||||
@@ -17,7 +17,7 @@ from frappe.utils import parse_addr | |||||
from frappe.utils import validate_email_add | from frappe.utils import validate_email_add | ||||
class Newsletter(Document): | |||||
class Newsletter(WebsiteGenerator): | |||||
def onload(self): | def onload(self): | ||||
if self.email_sent: | if self.email_sent: | ||||
self.get("__onload").status_count = dict(frappe.db.sql("""select status, count(name) | 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 | group by status""", (self.doctype, self.name))) or None | ||||
def validate(self): | def validate(self): | ||||
self.route = "newsletters/" + self.name | |||||
if self.send_from: | if self.send_from: | ||||
validate_email_add(self.send_from, True) | validate_email_add(self.send_from, True) | ||||
@@ -105,6 +106,26 @@ class Newsletter(Document): | |||||
throw(_("Please save the Newsletter before sending")) | throw(_("Please save the Newsletter before sending")) | ||||
check_email_limit(self.recipients) | 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): | def get_email_groups(name): | ||||
return frappe.db.get_all("Newsletter Email Group", ["email_group"],{"parent":name, "parenttype":"Newsletter"}) | 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() | 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) | |||||
@@ -0,0 +1,65 @@ | |||||
{% extends "templates/web.html" %} | |||||
{% block title %} {{ _("Newsletter") }} {% endblock %} | |||||
{% block page_content %} | |||||
<style> | |||||
.blog-container { | |||||
max-width: 720px; | |||||
margin: auto; | |||||
} | |||||
.blog-header { | |||||
font-weight: 700; | |||||
font-size: 1.5em; | |||||
} | |||||
.blog-info { | |||||
text-align:center; | |||||
margin-top: 30px; | |||||
} | |||||
.blog-text { | |||||
padding-top: 50px; | |||||
padding-bottom: 50px; | |||||
font-size: 15px; | |||||
line-height: 1.5; | |||||
} | |||||
.blog-text p { | |||||
margin-bottom: 30px; | |||||
} | |||||
</style> | |||||
<div class="blog-container"> | |||||
<article class="blog-content" itemscope> | |||||
<div class="blog-info"> | |||||
<h1 itemprop="headline" class="blog-header">{{ doc.subject }}</h1> | |||||
<p class="post-by text-muted"> | |||||
{{ frappe.format_date(doc.modified) }} | |||||
</p> | |||||
</div> | |||||
<div itemprop="articleBody" class="longform blog-text"> | |||||
{{ doc.message }} | |||||
</div> | |||||
</article> | |||||
{% if attachments %} | |||||
<div> | |||||
<div class="row text-muted"> | |||||
<div class="col-sm-12 h6 text-uppercase"> | |||||
{{ _("Attachments") }} | |||||
</div> | |||||
</div> | |||||
<div class="row"> | |||||
<div class="col-sm-12"> | |||||
{% for attachment in attachments %} | |||||
<p class="small"> | |||||
<a href="{{ attachment.file_url }}" target="blank"> | |||||
{{ attachment.file_name }} | |||||
</a> | |||||
</p> | |||||
{% endfor %} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{% endif %} | |||||
</div> | |||||
{% endblock %} |
@@ -0,0 +1,15 @@ | |||||
<div class="web-list-item transaction-list-item"> | |||||
<a href = "{{ route }}/"> | |||||
<div class="row"> | |||||
<div class="col-sm-8 text-left bold"> | |||||
{{ doc.subject }} | |||||
</div> | |||||
<div class="col-sm-4"> | |||||
<div class="text-muted text-right" | |||||
title="{{ frappe.utils.format_datetime(doc.modified, "medium") }}"> | |||||
{{ frappe.utils.pretty_date(doc.modified) }} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</a> | |||||
</div> |
@@ -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() | |||||
]); | |||||
}); |
@@ -7,24 +7,26 @@ import frappe, unittest | |||||
from frappe.email.doctype.newsletter.newsletter import confirmed_unsubscribe | from frappe.email.doctype.newsletter.newsletter import confirmed_unsubscribe | ||||
from six.moves.urllib.parse import unquote | from six.moves.urllib.parse import unquote | ||||
emails = ["test_subscriber1@example.com", "test_subscriber2@example.com", | emails = ["test_subscriber1@example.com", "test_subscriber2@example.com", | ||||
"test_subscriber3@example.com"] | |||||
"test_subscriber3@example.com", "test1@example.com"] | |||||
class TestNewsletter(unittest.TestCase): | class TestNewsletter(unittest.TestCase): | ||||
def setUp(self): | def setUp(self): | ||||
frappe.set_user("Administrator") | |||||
frappe.db.sql('delete from `tabEmail Group Member`') | frappe.db.sql('delete from `tabEmail Group Member`') | ||||
for email in emails: | for email in emails: | ||||
frappe.get_doc({ | frappe.get_doc({ | ||||
"doctype": "Email Group Member", | "doctype": "Email Group Member", | ||||
"email": email, | "email": email, | ||||
"email_group": "_Test Email Group" | "email_group": "_Test Email Group" | ||||
}).insert() | |||||
}).insert() | |||||
def test_send(self): | def test_send(self): | ||||
name = self.send_newsletter() | name = self.send_newsletter() | ||||
email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")] | 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] | recipients = [e.recipients[0].recipient for e in email_queue_list] | ||||
for email in emails: | for email in emails: | ||||
self.assertTrue(email in recipients) | self.assertTrue(email in recipients) | ||||
@@ -41,13 +43,14 @@ class TestNewsletter(unittest.TestCase): | |||||
name = self.send_newsletter() | name = self.send_newsletter() | ||||
email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")] | 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] | recipients = [e.recipients[0].recipient for e in email_queue_list] | ||||
for email in emails: | for email in emails: | ||||
if email != to_unsubscribe: | if email != to_unsubscribe: | ||||
self.assertTrue(email in recipients) | 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`") | ||||
frappe.db.sql("delete from `tabEmail Queue Recipient`") | frappe.db.sql("delete from `tabEmail Queue Recipient`") | ||||
frappe.db.sql("delete from `tabNewsletter`") | frappe.db.sql("delete from `tabNewsletter`") | ||||
@@ -55,7 +58,8 @@ class TestNewsletter(unittest.TestCase): | |||||
"doctype": "Newsletter", | "doctype": "Newsletter", | ||||
"subject": "_Test Newsletter", | "subject": "_Test Newsletter", | ||||
"send_from": "Test Sender <test_sender@example.com>", | "send_from": "Test Sender <test_sender@example.com>", | ||||
"message": "Testing my news." | |||||
"message": "Testing my news.", | |||||
"published": published | |||||
}).insert(ignore_permissions=True) | }).insert(ignore_permissions=True) | ||||
newsletter.append("email_group", {"email_group": "_Test Email Group"}) | newsletter.append("email_group", {"email_group": "_Test Email Group"}) | ||||
@@ -63,4 +67,21 @@ class TestNewsletter(unittest.TestCase): | |||||
newsletter.send_emails() | newsletter.send_emails() | ||||
return newsletter.name | 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"] | test_dependencies = ["Email Group"] |
@@ -1,6 +1,7 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
from . import __version__ as app_version | from . import __version__ as app_version | ||||
app_name = "frappe" | app_name = "frappe" | ||||
app_title = "Frappe Framework" | app_title = "Frappe Framework" | ||||
app_publisher = "Frappe Technologies" | app_publisher = "Frappe Technologies" | ||||
@@ -48,9 +49,11 @@ bootstrap = "assets/frappe/css/bootstrap.css" | |||||
web_include_css = [ | web_include_css = [ | ||||
"assets/css/frappe-web.css" | "assets/css/frappe-web.css" | ||||
] | ] | ||||
website_route_rules = [ | website_route_rules = [ | ||||
{"from_route": "/blog/<category>", "to_route": "Blog Post"}, | {"from_route": "/blog/<category>", "to_route": "Blog Post"}, | ||||
{"from_route": "/kb/<category>", "to_route": "Help Article"} | |||||
{"from_route": "/kb/<category>", "to_route": "Help Article"}, | |||||
{"from_route": "/newsletters", "to_route": "Newsletter"} | |||||
] | ] | ||||
write_file_keys = ["file_url", "file_name"] | write_file_keys = ["file_url", "file_name"] | ||||