Browse Source

Merge pull request #4059 from manassolanki/portal-newsletter

Portal for Newsletter
version-14
Rushabh Mehta 7 years ago
committed by GitHub
parent
commit
8c078dd839
7 changed files with 258 additions and 13 deletions
  1. +81
    -4
      frappe/email/doctype/newsletter/newsletter.json
  2. +43
    -2
      frappe/email/doctype/newsletter/newsletter.py
  3. +65
    -0
      frappe/email/doctype/newsletter/templates/newsletter.html
  4. +15
    -0
      frappe/email/doctype/newsletter/templates/newsletter_row.html
  5. +23
    -0
      frappe/email/doctype/newsletter/test_newsletter.js
  6. +27
    -6
      frappe/email/doctype/newsletter/test_newsletter.py
  7. +4
    -1
      frappe/hooks.py

+ 81
- 4
frappe/email/doctype/newsletter/newsletter.json View File

@@ -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",


+ 43
- 2
frappe/email/doctype/newsletter/newsletter.py View File

@@ -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)


+ 65
- 0
frappe/email/doctype/newsletter/templates/newsletter.html View File

@@ -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 %}

+ 15
- 0
frappe/email/doctype/newsletter/templates/newsletter_row.html View File

@@ -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>

+ 23
- 0
frappe/email/doctype/newsletter/test_newsletter.js View File

@@ -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()
]);

});

+ 27
- 6
frappe/email/doctype/newsletter/test_newsletter.py View File

@@ -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 <test_sender@example.com>",
"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"]

+ 4
- 1
frappe/hooks.py View File

@@ -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/<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"]


Loading…
Cancel
Save