From c4dbf971fba9c845aba7d698bb62451099928b68 Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Mon, 8 Feb 2010 12:02:54 +0000
Subject: [PATCH 01/13] author_notification field added.
svn path=/plone.app.discussion/branches/notification/; revision=33867
---
plone/app/discussion/browser/comments.py | 11 ++++++-----
plone/app/discussion/comment.py | 24 ++++++++++++++++++++++++
plone/app/discussion/interfaces.py | 2 ++
3 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py
index 3b0fa84..8ad3925 100644
--- a/plone/app/discussion/browser/comments.py
+++ b/plone/app/discussion/browser/comments.py
@@ -19,6 +19,7 @@ from zope.viewlet.interfaces import IViewlet
from z3c.form import form, field, button, interfaces, widget
from z3c.form.browser.textarea import TextAreaWidget
+from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
@@ -55,19 +56,19 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
'modification_date',
'author_username')
+ def updateFields(self):
+ self.fields['author_notification'].widgetFactory = SingleCheckBoxFieldWidget
+ self.move('author_notification', after='text')
+
def updateWidgets(self):
super(CommentForm, self).updateWidgets()
self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
portal_membership = getToolByName(self.context, 'portal_membership')
self.widgets['text'].addClass("autoresize")
+ self.widgets['author_notification'].label = _(u"")
if not portal_membership.isAnonymousUser():
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
- # XXX: Since we are not using the author_email field in the
- # current state, we hide it by default. But we keep the field for
- # integrators or later use.
- self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
-
def updateActions(self):
super(CommentForm, self).updateActions()
diff --git a/plone/app/discussion/comment.py b/plone/app/discussion/comment.py
index 7a39a8c..9a6cb61 100644
--- a/plone/app/discussion/comment.py
+++ b/plone/app/discussion/comment.py
@@ -61,6 +61,8 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
author_name = None
author_email = None
+
+ author_notification = None
# Note: we want to use zope.component.createObject() to instantiate
# comments as far as possible. comment_id and __parent__ are set via
@@ -126,3 +128,25 @@ def notify_content_object(obj, event):
"""
content_obj = aq_parent(aq_parent(obj))
content_obj.reindexObject(idxs=('total_comments', 'last_comment_date', 'commentators',))
+
+def notify_user(obj, event):
+ """Tell the user when a comment is added
+ """
+ acl_users = getToolByName(obj, 'acl_users')
+ mail_host = getToolByName(obj, 'MailHost')
+ portal_url = getToolByName(obj, 'portal_url')
+
+ portal = portal_url.getPortalObject()
+ sender = portal.getProperty('email_from_address')
+
+ if not sender:
+ return
+
+ subject = "Is this you?"
+ message = "A presenter called %s was added here %s" % (obj.title, obj.absolute_url(),)
+
+ matching_users = acl_users.searchUsers(fullname=obj.title)
+ for user_info in matching_users:
+ email = user_info.get('email', None)
+ if email is not None:
+ mail_host.secureSend(message, email, sender, subject)
\ No newline at end of file
diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py
index 08f7b73..df06665 100644
--- a/plone/app/discussion/interfaces.py
+++ b/plone/app/discussion/interfaces.py
@@ -171,6 +171,8 @@ class IComment(Interface):
# for anonymous comments only, set to None for logged in comments
author_name = schema.TextLine(title=_(u"Name"), required=False)
author_email = schema.TextLine(title=_(u"Email"), required=False)
+
+ author_notification = schema.Bool(title=_("Notify me of new posts via email"), required=False)
title = schema.TextLine(title=_(u"Subject"))
From de3bb53c1397c6d567ae16076b47789c6f2ca250 Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Mon, 8 Feb 2010 14:47:53 +0000
Subject: [PATCH 02/13] move author notification field to the bottom of the
form.
svn path=/plone.app.discussion/branches/notification/; revision=33868
---
plone/app/discussion/browser/comments.py | 1 -
plone/app/discussion/interfaces.py | 4 ++--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py
index 8ad3925..2887a91 100644
--- a/plone/app/discussion/browser/comments.py
+++ b/plone/app/discussion/browser/comments.py
@@ -58,7 +58,6 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
def updateFields(self):
self.fields['author_notification'].widgetFactory = SingleCheckBoxFieldWidget
- self.move('author_notification', after='text')
def updateWidgets(self):
super(CommentForm, self).updateWidgets()
diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py
index df06665..2cfb86b 100644
--- a/plone/app/discussion/interfaces.py
+++ b/plone/app/discussion/interfaces.py
@@ -172,13 +172,13 @@ class IComment(Interface):
author_name = schema.TextLine(title=_(u"Name"), required=False)
author_email = schema.TextLine(title=_(u"Email"), required=False)
- author_notification = schema.Bool(title=_("Notify me of new posts via email"), required=False)
-
title = schema.TextLine(title=_(u"Subject"))
mime_type = schema.ASCIILine(title=_(u"MIME type"), default="text/plain")
text = schema.Text(title=_(u"Comment"))
+ author_notification = schema.Bool(title=_("Notify me of new posts via email"), required=False)
+
creator = schema.TextLine(title=_(u"Author name (for display)"))
creation_date = schema.Date(title=_(u"Creation date"))
modification_date = schema.Date(title=_(u"Modification date"))
From 26031cd1f497d31f0aaf0e87fbad02c45e520b5e Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Mon, 8 Feb 2010 15:02:17 +0000
Subject: [PATCH 03/13] notification enabled field added to discussion control
panel.
svn path=/plone.app.discussion/branches/notification/; revision=33869
---
plone/app/discussion/browser/comments.py | 6 +++++-
plone/app/discussion/browser/controlpanel.py | 2 ++
plone/app/discussion/interfaces.py | 11 +++++++++++
3 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py
index 2887a91..6c7cd44 100644
--- a/plone/app/discussion/browser/comments.py
+++ b/plone/app/discussion/browser/comments.py
@@ -62,12 +62,16 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
def updateWidgets(self):
super(CommentForm, self).updateWidgets()
self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
- portal_membership = getToolByName(self.context, 'portal_membership')
self.widgets['text'].addClass("autoresize")
self.widgets['author_notification'].label = _(u"")
+ portal_membership = getToolByName(self.context, 'portal_membership')
if not portal_membership.isAnonymousUser():
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
+ registry = queryUtility(IRegistry)
+ settings = registry.forInterface(IDiscussionSettings)
+ if not settings.notification_enabled:
+ self.widgets['author_notification'].mode = interfaces.HIDDEN_MODE
def updateActions(self):
super(CommentForm, self).updateActions()
diff --git a/plone/app/discussion/browser/controlpanel.py b/plone/app/discussion/browser/controlpanel.py
index b7d0c91..8bd3892 100644
--- a/plone/app/discussion/browser/controlpanel.py
+++ b/plone/app/discussion/browser/controlpanel.py
@@ -29,12 +29,14 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
self.fields['globally_enabled'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['anonymous_comments'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['show_commenter_image'].widgetFactory = SingleCheckBoxFieldWidget
+ self.fields['notification_enabled'].widgetFactory = SingleCheckBoxFieldWidget
def updateWidgets(self):
super(DiscussionSettingsEditForm, self).updateWidgets()
self.widgets['globally_enabled'].label = _(u"Enable Comments")
self.widgets['anonymous_comments'].label = _(u"Anonymous Comments")
self.widgets['show_commenter_image'].label = _(u"Commenter Image")
+ self.widgets['notification_enabled'].label = _(u"Email Notification")
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py
index 2cfb86b..8e0ca54 100644
--- a/plone/app/discussion/interfaces.py
+++ b/plone/app/discussion/interfaces.py
@@ -55,6 +55,17 @@ class IDiscussionSettings(Interface):
required=False,
default=True)
+ notification_enabled = schema.Bool(
+ title=_(u"label_notification_enabled",
+ default=u"Enable email notification"),
+ description=_(u"help_notification_enabled",
+ default=u"If selected, users can "
+ "choose to be notified "
+ "of new comments by "
+ "email."),
+ required=False,
+ default=False)
+
class IConversation(IIterableMapping):
"""A conversation about a content object.
From 67b5ff566eaefa6b69022a3d50faa13e17d1e858 Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Mon, 8 Feb 2010 18:28:03 +0000
Subject: [PATCH 04/13] fix singlecheckboxwidget for comment form.
svn path=/plone.app.discussion/branches/notification/; revision=33873
---
plone/app/discussion/browser/comments.py | 21 +++++++++++++------
plone/app/discussion/interfaces.py | 7 +++++--
.../discussion/tests/test_comments_viewlet.py | 2 +-
.../app/discussion/tests/test_controlpanel.py | 7 +++++++
4 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py
index 6c7cd44..f01b28e 100644
--- a/plone/app/discussion/browser/comments.py
+++ b/plone/app/discussion/browser/comments.py
@@ -57,21 +57,29 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
'author_username')
def updateFields(self):
+ super(CommentForm, self).updateFields()
self.fields['author_notification'].widgetFactory = SingleCheckBoxFieldWidget
-
+
def updateWidgets(self):
super(CommentForm, self).updateWidgets()
+
+ # Widgets
self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
self.widgets['text'].addClass("autoresize")
- self.widgets['author_notification'].label = _(u"")
+ self.widgets['author_notification'].label = _(u"")
+
+ # Anonymous / Logged-in
portal_membership = getToolByName(self.context, 'portal_membership')
if not portal_membership.isAnonymousUser():
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
- registry = queryUtility(IRegistry)
- settings = registry.forInterface(IDiscussionSettings)
- if not settings.notification_enabled:
- self.widgets['author_notification'].mode = interfaces.HIDDEN_MODE
+
+ # Notification enabled
+ # XXX: Not working! ComponentLookupError
+ #registry = queryUtility(IRegistry)
+ #settings = registry.forInterface(IDiscussionSettings)
+ #if not settings.notification_enabled:
+ # self.widgets['author_notification'].mode = interfaces.HIDDEN_MODE
def updateActions(self):
super(CommentForm, self).updateActions()
@@ -90,6 +98,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
author_name = u""
author_username = u""
author_email = u""
+ author_notification = None
# Captcha check for anonymous users (if Captcha is enabled)
registry = queryUtility(IRegistry)
diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py
index 8e0ca54..38b284a 100644
--- a/plone/app/discussion/interfaces.py
+++ b/plone/app/discussion/interfaces.py
@@ -168,7 +168,8 @@ class IComment(Interface):
Comments are indexed in the catalog and subject to workflow and security.
"""
- portal_type = schema.ASCIILine(title=_(u"Portal type"), default="Discussion Item")
+ portal_type = schema.ASCIILine(title=_(u"Portal type"),
+ default="Discussion Item")
__parent__ = schema.Object(title=_(u"Conversation"), schema=Interface)
__name__ = schema.TextLine(title=_(u"Name"))
@@ -188,7 +189,9 @@ class IComment(Interface):
mime_type = schema.ASCIILine(title=_(u"MIME type"), default="text/plain")
text = schema.Text(title=_(u"Comment"))
- author_notification = schema.Bool(title=_("Notify me of new posts via email"), required=False)
+ author_notification = schema.Bool(title=_(u"Notify me of new comments via "
+ "email."),
+ required=False)
creator = schema.TextLine(title=_(u"Author name (for display)"))
creation_date = schema.Date(title=_(u"Creation date"))
diff --git a/plone/app/discussion/tests/test_comments_viewlet.py b/plone/app/discussion/tests/test_comments_viewlet.py
index 98c5369..b255e24 100644
--- a/plone/app/discussion/tests/test_comments_viewlet.py
+++ b/plone/app/discussion/tests/test_comments_viewlet.py
@@ -86,7 +86,7 @@ class TestCommentsViewletIntegration(FunctionalTestCase):
browser.getLink(id='document').click()
browser.getControl(name='title').value = "Doc1"
browser.getControl(name='allowDiscussion:boolean').value = True
- browser.getControl(name='form.button.save').click()
+ browser.getControl(name='form.button.save').click()
doc1 = self.portal['doc1']
doc1_url = doc1.absolute_url()
diff --git a/plone/app/discussion/tests/test_controlpanel.py b/plone/app/discussion/tests/test_controlpanel.py
index 160c130..cc13f93 100644
--- a/plone/app/discussion/tests/test_controlpanel.py
+++ b/plone/app/discussion/tests/test_controlpanel.py
@@ -61,5 +61,12 @@ class RegistryTest(PloneTestCase):
self.failUnless('show_commenter_image' in IDiscussionSettings)
self.assertEquals(self.registry['plone.app.discussion.interfaces.IDiscussionSettings.show_commenter_image'], True)
+ def test_notification_enabled(self):
+ # Check show_commenter_image record
+ show_commenter_image = self.registry.records['plone.app.discussion.interfaces.IDiscussionSettings.notification_enabled']
+
+ self.failUnless('notification_enabled' in IDiscussionSettings)
+ self.assertEquals(self.registry['plone.app.discussion.interfaces.IDiscussionSettings.notification_enabled'], False)
+
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
\ No newline at end of file
From f0fb6514c782a4b23bddb241f1a59c123e6176e3 Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Thu, 11 Feb 2010 21:05:28 +0000
Subject: [PATCH 05/13] Moderator email notification added: As a moderator, I
am notified when new comments require my attention.
svn path=/plone.app.discussion/branches/notification/; revision=33921
---
plone/app/discussion/browser/controlpanel.py | 2 +
plone/app/discussion/comment.py | 66 ++++++++--
plone/app/discussion/interfaces.py | 13 ++
plone/app/discussion/subscribers.zcml | 12 ++
plone/app/discussion/tests/test_migration.py | 2 +
.../discussion/tests/test_notifications.py | 124 ++++++++++++++++++
6 files changed, 208 insertions(+), 11 deletions(-)
create mode 100644 plone/app/discussion/tests/test_notifications.py
diff --git a/plone/app/discussion/browser/controlpanel.py b/plone/app/discussion/browser/controlpanel.py
index 8bd3892..7f1e01d 100644
--- a/plone/app/discussion/browser/controlpanel.py
+++ b/plone/app/discussion/browser/controlpanel.py
@@ -29,6 +29,7 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
self.fields['globally_enabled'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['anonymous_comments'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['show_commenter_image'].widgetFactory = SingleCheckBoxFieldWidget
+ self.fields['moderator_notification_enabled'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['notification_enabled'].widgetFactory = SingleCheckBoxFieldWidget
def updateWidgets(self):
@@ -36,6 +37,7 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
self.widgets['globally_enabled'].label = _(u"Enable Comments")
self.widgets['anonymous_comments'].label = _(u"Anonymous Comments")
self.widgets['show_commenter_image'].label = _(u"Commenter Image")
+ self.widgets['moderator_notification_enabled'].label = _(u"Moderator Email Notification")
self.widgets['notification_enabled'].label = _(u"Email Notification")
diff --git a/plone/app/discussion/comment.py b/plone/app/discussion/comment.py
index 9a6cb61..1f7ac35 100644
--- a/plone/app/discussion/comment.py
+++ b/plone/app/discussion/comment.py
@@ -1,10 +1,14 @@
"""The default comment class and factory.
"""
from datetime import datetime
+
from zope.interface import implements
+
from zope.component.factory import Factory
+from zope.component import queryUtility
from Acquisition import aq_parent, Implicit
+
from AccessControl.Role import RoleManager
from AccessControl.Owned import Owned
@@ -15,7 +19,9 @@ from Products.CMFCore.utils import getToolByName
from OFS.Traversable import Traversable
-from plone.app.discussion.interfaces import IComment
+from plone.registry.interfaces import IRegistry
+
+from plone.app.discussion.interfaces import IComment, IDiscussionSettings
try:
# Plone 4:
@@ -127,26 +133,64 @@ def notify_content_object(obj, event):
"""Tell the content object when a comment is added
"""
content_obj = aq_parent(aq_parent(obj))
- content_obj.reindexObject(idxs=('total_comments', 'last_comment_date', 'commentators',))
+ content_obj.reindexObject(idxs=('total_comments',
+ 'last_comment_date',
+ 'commentators',))
def notify_user(obj, event):
"""Tell the user when a comment is added
"""
- acl_users = getToolByName(obj, 'acl_users')
+ conversation = aq_parent(obj)
+ content_object = aq_parent(conversation)
mail_host = getToolByName(obj, 'MailHost')
portal_url = getToolByName(obj, 'portal_url')
portal = portal_url.getPortalObject()
sender = portal.getProperty('email_from_address')
-
+
if not sender:
return
- subject = "Is this you?"
- message = "A presenter called %s was added here %s" % (obj.title, obj.absolute_url(),)
+ for comment in conversation.getComments():
+ if comment.author_notification and comment.author_email:
+ subject = "A comment has been posted."
+ message = "A comment with the title '%s' has been posted here: %s" \
+ % (obj.title,
+ content_object.absolute_url(),)
+ mail_host.send(message, comment.author_email, sender, subject)
+
+def notify_moderator(obj, index):
+ """Tell the moderator when a comment needs attention
+ """
- matching_users = acl_users.searchUsers(fullname=obj.title)
- for user_info in matching_users:
- email = user_info.get('email', None)
- if email is not None:
- mail_host.secureSend(message, email, sender, subject)
\ No newline at end of file
+ # check if notification is enabled
+ registry = queryUtility(IRegistry)
+ settings = registry.forInterface(IDiscussionSettings)
+ if not settings.moderator_notification_enabled:
+ return
+
+ # check if comment review workflow is enabled
+ wf = getToolByName(obj, 'portal_workflow')
+ if wf.getChainForPortalType('Discussion Item') != \
+ ('comment_review_workflow',):
+ return
+
+ mail_host = getToolByName(obj, 'MailHost')
+ portal_url = getToolByName(obj, 'portal_url')
+ portal = portal_url.getPortalObject()
+ sender = portal.getProperty('email_from_address')
+ mto = portal.getProperty('email_from_address')
+
+ # check if a sender address is available
+ if not sender:
+ return
+
+ conversation = aq_parent(obj)
+ content_object = aq_parent(conversation)
+
+ comment = conversation.getComments().next()
+ subject = "A comment has been posted."
+ message = "A comment with the title '%s' has been posted here: %s" \
+ % (obj.title,
+ content_object.absolute_url(),)
+ mail_host.send(message, mto, sender, subject)
diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py
index 38b284a..9740270 100644
--- a/plone/app/discussion/interfaces.py
+++ b/plone/app/discussion/interfaces.py
@@ -55,6 +55,19 @@ class IDiscussionSettings(Interface):
required=False,
default=True)
+ moderator_notification_enabled = schema.Bool(
+ title=_(u"label_moderator_notification_enabled",
+ default=u"Enable moderator email notification"),
+ description=_(u"help_moderator_notification_enabled",
+ default=u"If selected, "
+ "the moderator "
+ "is notified "
+ "if a comment "
+ "needs "
+ "attention."),
+ required=False,
+ default=False)
+
notification_enabled = schema.Bool(
title=_(u"label_notification_enabled",
default=u"Enable email notification"),
diff --git a/plone/app/discussion/subscribers.zcml b/plone/app/discussion/subscribers.zcml
index 1be80e1..8b23638 100644
--- a/plone/app/discussion/subscribers.zcml
+++ b/plone/app/discussion/subscribers.zcml
@@ -37,6 +37,12 @@
handler=".tool.unindex_object"
/>
+
+
@@ -71,6 +77,12 @@
handler=".tool.unindex_object"
/>
+
+
diff --git a/plone/app/discussion/tests/test_migration.py b/plone/app/discussion/tests/test_migration.py
index 32b8793..a5802d7 100644
--- a/plone/app/discussion/tests/test_migration.py
+++ b/plone/app/discussion/tests/test_migration.py
@@ -17,6 +17,8 @@ from plone.app.discussion.interfaces import IConversation, IComment
class MigrationTest(PloneTestCase):
+ layer = DiscussionLayer
+
def afterSetUp(self):
self.loginAsPortalOwner()
typetool = self.portal.portal_types
diff --git a/plone/app/discussion/tests/test_notifications.py b/plone/app/discussion/tests/test_notifications.py
new file mode 100644
index 0000000..035869f
--- /dev/null
+++ b/plone/app/discussion/tests/test_notifications.py
@@ -0,0 +1,124 @@
+import unittest
+
+from Acquisition import aq_base
+
+from zope.app.container.contained import ObjectAddedEvent
+from zope.app.container.interfaces import IObjectAddedEvent
+
+from zope.component import createObject
+from zope.component import getSiteManager
+from zope.component import queryUtility
+
+from Products.PloneTestCase.ptc import PloneTestCase
+
+from Products.MailHost.interfaces import IMailHost
+from Products.CMFPlone.tests.utils import MockMailHost
+
+from plone.registry.interfaces import IRegistry
+
+from plone.app.discussion.comment import notify_user
+from plone.app.discussion.interfaces import IComment, IConversation, IReplies
+from plone.app.discussion.interfaces import IDiscussionSettings
+from plone.app.discussion.tests.layer import DiscussionLayer
+
+
+class TestModeratorNotificationUnit(PloneTestCase):
+
+ layer = DiscussionLayer
+
+ def afterSetUp(self):
+ # Set up a mock mailhost
+ self.portal._original_MailHost = self.portal.MailHost
+ self.portal.MailHost = mailhost = MockMailHost('MailHost')
+ sm = getSiteManager(context=self.portal)
+ sm.unregisterUtility(provided=IMailHost)
+ sm.registerUtility(mailhost, provided=IMailHost)
+
+ # We need to fake a valid mail setup
+ self.portal.email_from_address = "portal@plone.test"
+ self.mailhost = self.portal.MailHost
+
+ # Enable comment moderation
+ self.portal.portal_types['Document'].allow_discussion = True
+ self.portal.portal_workflow.setChainForPortalTypes(
+ ('Discussion Item',),
+ ('comment_review_workflow',))
+
+ # Enable moderator notification setting
+ registry = queryUtility(IRegistry)
+ settings = registry.forInterface(IDiscussionSettings)
+ registry['plone.app.discussion.interfaces.IDiscussionSettings.moderator_notification_enabled'] = True
+
+ self.loginAsPortalOwner()
+ self.portal.invokeFactory('Document', 'doc1')
+ self.portal_discussion = self.portal.portal_discussion
+ self.conversation = IConversation(self.portal.doc1)
+
+ def beforeTearDown(self):
+ self.portal.MailHost = self.portal._original_MailHost
+ sm = getSiteManager(context=self.portal)
+ sm.unregisterUtility(provided=IMailHost)
+ sm.registerUtility(aq_base(self.portal._original_MailHost), provided=IMailHost)
+
+ def test_notify_moderator(self):
+ # Add a comment and make sure an email is send to the moderator.
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 1'
+ comment.text = 'Comment text'
+ self.conversation.addComment(comment)
+
+ self.assertEquals(len(self.mailhost.messages), 1)
+ self.failUnless(self.mailhost.messages[0])
+ msg = self.mailhost.messages[0]
+ self.failUnless('To: portal@plone.test' in msg)
+ self.failUnless('From: portal@plone.test' in msg)
+
+ #We expect the headers to be properly header encoded (7-bit):
+ #>>> 'Subject: =?utf-8?q?Some_t=C3=A4st_subject=2E?=' in msg
+ #True
+
+ #The output should be encoded in a reasonable manner (in this case quoted-printable):
+ #>>> msg
+ #'...Another t=C3=A4st message...You are receiving this mail because T=C3=A4st user\ntest@plone.test...is sending feedback about the site you administer at...
+
+ def test_do_not_notify_moderator_when_no_sender_is_available(self):
+ # Set sender mail address to nonw and make sure no email is send to the
+ # moderator.
+ self.portal.email_from_address = None
+
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 1'
+ comment.text = 'Comment text'
+ self.conversation.addComment(comment)
+ self.assertEquals(len(self.mailhost.messages), 0)
+
+ def test_do_not_notify_moderator_when_notification_is_disabled(self):
+ # Disable moderator notification setting and make sure no email is send
+ # to the moderator.
+ registry = queryUtility(IRegistry)
+ settings = registry.forInterface(IDiscussionSettings)
+ registry['plone.app.discussion.interfaces.IDiscussionSettings.moderator_notification_enabled'] = False
+
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 1'
+ comment.text = 'Comment text'
+ self.conversation.addComment(comment)
+ self.assertEquals(len(self.mailhost.messages), 0)
+
+ def test_do_not_notify_moderator_when_moderation_workflow_is_disabled(self):
+ # Disable comment moderation and make sure no email is send to the
+ # moderator.
+ self.portal.portal_types['Document'].allow_discussion = True
+ self.portal.portal_workflow.setChainForPortalTypes(
+ ('Discussion Item',),
+ ('simple_publication_workflow',))
+
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 1'
+ comment.text = 'Comment text'
+ self.conversation.addComment(comment)
+ self.assertEquals(len(self.mailhost.messages), 0)
+
+def test_suite():
+ return unittest.defaultTestLoader.loadTestsFromName(__name__)
+
\ No newline at end of file
From 6e46ce7581af24f71dc4ca0410d759b6c8c86fa0 Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Fri, 12 Feb 2010 10:11:01 +0000
Subject: [PATCH 06/13] test moderator notification enabled setting.
svn path=/plone.app.discussion/branches/notification/; revision=33928
---
plone/app/discussion/tests/test_controlpanel.py | 7 +++++++
plone/app/discussion/tests/test_notifications.py | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/plone/app/discussion/tests/test_controlpanel.py b/plone/app/discussion/tests/test_controlpanel.py
index cc13f93..31e2368 100644
--- a/plone/app/discussion/tests/test_controlpanel.py
+++ b/plone/app/discussion/tests/test_controlpanel.py
@@ -61,6 +61,13 @@ class RegistryTest(PloneTestCase):
self.failUnless('show_commenter_image' in IDiscussionSettings)
self.assertEquals(self.registry['plone.app.discussion.interfaces.IDiscussionSettings.show_commenter_image'], True)
+ def test_moderator_notification_enabled(self):
+ # Check show_commenter_image record
+ show_commenter_image = self.registry.records['plone.app.discussion.interfaces.IDiscussionSettings.moderator_notification_enabled']
+
+ self.failUnless('moderator_notification_enabled' in IDiscussionSettings)
+ self.assertEquals(self.registry['plone.app.discussion.interfaces.IDiscussionSettings.moderator_notification_enabled'], False)
+
def test_notification_enabled(self):
# Check show_commenter_image record
show_commenter_image = self.registry.records['plone.app.discussion.interfaces.IDiscussionSettings.notification_enabled']
diff --git a/plone/app/discussion/tests/test_notifications.py b/plone/app/discussion/tests/test_notifications.py
index 035869f..6493ef3 100644
--- a/plone/app/discussion/tests/test_notifications.py
+++ b/plone/app/discussion/tests/test_notifications.py
@@ -118,7 +118,7 @@ class TestModeratorNotificationUnit(PloneTestCase):
comment.text = 'Comment text'
self.conversation.addComment(comment)
self.assertEquals(len(self.mailhost.messages), 0)
-
+
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
\ No newline at end of file
From 38fd65f46df5ff844bb9559c707b292113be4c1a Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Fri, 12 Feb 2010 10:21:39 +0000
Subject: [PATCH 07/13] remove whitespace to make coverage-test happy.
svn path=/plone.app.discussion/branches/notification/; revision=33929
---
plone/app/discussion/tests/test_notifications.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plone/app/discussion/tests/test_notifications.py b/plone/app/discussion/tests/test_notifications.py
index 6493ef3..39b9601 100644
--- a/plone/app/discussion/tests/test_notifications.py
+++ b/plone/app/discussion/tests/test_notifications.py
@@ -121,4 +121,4 @@ class TestModeratorNotificationUnit(PloneTestCase):
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
-
\ No newline at end of file
+
From be3d347471e1fd269263e6dc72b2d0e702e06ef4 Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Sat, 13 Feb 2010 21:31:17 +0000
Subject: [PATCH 08/13] user email notification added.
svn path=/plone.app.discussion/branches/notification/; revision=33963
---
plone/app/discussion/browser/controlpanel.py | 4 +-
plone/app/discussion/comment.py | 27 +++--
plone/app/discussion/interfaces.py | 11 ++-
plone/app/discussion/subscribers.zcml | 12 +++
.../app/discussion/tests/test_controlpanel.py | 8 +-
.../discussion/tests/test_notifications.py | 99 ++++++++++++++++++-
6 files changed, 140 insertions(+), 21 deletions(-)
diff --git a/plone/app/discussion/browser/controlpanel.py b/plone/app/discussion/browser/controlpanel.py
index 7f1e01d..ec4bc20 100644
--- a/plone/app/discussion/browser/controlpanel.py
+++ b/plone/app/discussion/browser/controlpanel.py
@@ -30,7 +30,7 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
self.fields['anonymous_comments'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['show_commenter_image'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['moderator_notification_enabled'].widgetFactory = SingleCheckBoxFieldWidget
- self.fields['notification_enabled'].widgetFactory = SingleCheckBoxFieldWidget
+ self.fields['user_notification_enabled'].widgetFactory = SingleCheckBoxFieldWidget
def updateWidgets(self):
super(DiscussionSettingsEditForm, self).updateWidgets()
@@ -38,7 +38,7 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
self.widgets['anonymous_comments'].label = _(u"Anonymous Comments")
self.widgets['show_commenter_image'].label = _(u"Commenter Image")
self.widgets['moderator_notification_enabled'].label = _(u"Moderator Email Notification")
- self.widgets['notification_enabled'].label = _(u"Email Notification")
+ self.widgets['user_notification_enabled'].label = _(u"User Email Notification")
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
diff --git a/plone/app/discussion/comment.py b/plone/app/discussion/comment.py
index 1f7ac35..311143e 100644
--- a/plone/app/discussion/comment.py
+++ b/plone/app/discussion/comment.py
@@ -140,24 +140,33 @@ def notify_content_object(obj, event):
def notify_user(obj, event):
"""Tell the user when a comment is added
"""
- conversation = aq_parent(obj)
- content_object = aq_parent(conversation)
+
+ # check if notification is enabled
+ registry = queryUtility(IRegistry)
+ settings = registry.forInterface(IDiscussionSettings)
+ if not settings.user_notification_enabled:
+ return
+
mail_host = getToolByName(obj, 'MailHost')
portal_url = getToolByName(obj, 'portal_url')
-
portal = portal_url.getPortalObject()
sender = portal.getProperty('email_from_address')
if not sender:
return
+
+ conversation = aq_parent(obj)
+ content_object = aq_parent(conversation)
for comment in conversation.getComments():
- if comment.author_notification and comment.author_email:
- subject = "A comment has been posted."
- message = "A comment with the title '%s' has been posted here: %s" \
- % (obj.title,
- content_object.absolute_url(),)
- mail_host.send(message, comment.author_email, sender, subject)
+ if obj != comment and \
+ comment.author_notification and \
+ comment.author_email:
+ subject = "A comment has been posted."
+ message = "A comment with the title '%s' has been posted here: %s" \
+ % (obj.title,
+ content_object.absolute_url(),)
+ mail_host.send(message, comment.author_email, sender, subject)
def notify_moderator(obj, index):
"""Tell the moderator when a comment needs attention
diff --git a/plone/app/discussion/interfaces.py b/plone/app/discussion/interfaces.py
index 9740270..00354bf 100644
--- a/plone/app/discussion/interfaces.py
+++ b/plone/app/discussion/interfaces.py
@@ -68,17 +68,18 @@ class IDiscussionSettings(Interface):
required=False,
default=False)
- notification_enabled = schema.Bool(
- title=_(u"label_notification_enabled",
- default=u"Enable email notification"),
- description=_(u"help_notification_enabled",
+ user_notification_enabled = schema.Bool(
+ title=_(u"label_user_notification_enabled",
+ default=u"Enable user email notification"),
+ description=_(u"help_user_notification_enabled",
default=u"If selected, users can "
"choose to be notified "
"of new comments by "
"email."),
required=False,
default=False)
-
+
+
class IConversation(IIterableMapping):
"""A conversation about a content object.
diff --git a/plone/app/discussion/subscribers.zcml b/plone/app/discussion/subscribers.zcml
index 8b23638..8b50424 100644
--- a/plone/app/discussion/subscribers.zcml
+++ b/plone/app/discussion/subscribers.zcml
@@ -37,6 +37,12 @@
handler=".tool.unindex_object"
/>
+
+
+
+
>> 'Subject: =?utf-8?q?Some_t=C3=A4st_subject=2E?=' in msg
+ #True
+
+ #The output should be encoded in a reasonable manner (in this case quoted-printable):
+ #>>> msg
+ #'...Another t=C3=A4st message...You are receiving this mail because T=C3=A4st user\ntest@plone.test...is sending feedback about the site you administer at...
+
+ def test_do_not_notify_user_when_notification_is_disabled(self):
+ # Disable user notification and make sure no email is send to the user.
+ registry = queryUtility(IRegistry)
+ settings = registry.forInterface(IDiscussionSettings)
+ registry['plone.app.discussion.interfaces.IDiscussionSettings.user_notification_enabled'] = False
+
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 1'
+ comment.text = 'Comment text'
+ comment.author_notification = True
+ comment.author_email = "john@plone.test"
+ self.conversation.addComment(comment)
+
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 2'
+ comment.text = 'Comment text'
+ self.conversation.addComment(comment)
+
+ self.assertEquals(len(self.mailhost.messages), 0)
+
+ def test_do_not_notify_user_when_email_address_is_given(self):
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 1'
+ comment.text = 'Comment text'
+ comment.author_notification = True
+ self.conversation.addComment(comment)
+
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 2'
+ comment.text = 'Comment text'
+ self.conversation.addComment(comment)
+
+ self.assertEquals(len(self.mailhost.messages), 0)
+
+
class TestModeratorNotificationUnit(PloneTestCase):
layer = DiscussionLayer
@@ -49,6 +146,7 @@ class TestModeratorNotificationUnit(PloneTestCase):
settings = registry.forInterface(IDiscussionSettings)
registry['plone.app.discussion.interfaces.IDiscussionSettings.moderator_notification_enabled'] = True
+ # Create test content
self.loginAsPortalOwner()
self.portal.invokeFactory('Document', 'doc1')
self.portal_discussion = self.portal.portal_discussion
@@ -121,4 +219,3 @@ class TestModeratorNotificationUnit(PloneTestCase):
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
-
From 90c44a7c676d10428b11fc1f3c32c290a749530f Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Sat, 13 Feb 2010 21:44:26 +0000
Subject: [PATCH 09/13] test do not notify user when no sender is available.
svn path=/plone.app.discussion/branches/notification/; revision=33964
---
.../app/discussion/tests/test_notifications.py | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/plone/app/discussion/tests/test_notifications.py b/plone/app/discussion/tests/test_notifications.py
index 281e3fe..d4b92d0 100644
--- a/plone/app/discussion/tests/test_notifications.py
+++ b/plone/app/discussion/tests/test_notifications.py
@@ -118,6 +118,24 @@ class TestUserNotificationUnit(PloneTestCase):
self.assertEquals(len(self.mailhost.messages), 0)
+ def test_do_not_notify_user_when_no_sender_is_available(self):
+ # Set sender mail address to nonw and make sure no email is send to the
+ # moderator.
+ self.portal.email_from_address = None
+
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 1'
+ comment.text = 'Comment text'
+ comment.author_notification = True
+ comment.author_email = "john@plone.test"
+ self.conversation.addComment(comment)
+
+ comment = createObject('plone.Comment')
+ comment.title = 'Comment 2'
+ comment.text = 'Comment text'
+ self.conversation.addComment(comment)
+
+ self.assertEquals(len(self.mailhost.messages), 0)
class TestModeratorNotificationUnit(PloneTestCase):
From 57b41454bb8021732b8659decff7a02c4c1e1f34 Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Tue, 16 Feb 2010 20:25:56 +0000
Subject: [PATCH 10/13] update CommentsViewlet to follow best practice for
showing a z3c.form inside a Viewlet. Override update method instead of init
method.
svn path=/plone.app.discussion/branches/notification/; revision=34052
---
plone/app/discussion/browser/comments.pt | 2 +-
plone/app/discussion/browser/comments.py | 25 +++++++++---------------
2 files changed, 10 insertions(+), 17 deletions(-)
diff --git a/plone/app/discussion/browser/comments.pt b/plone/app/discussion/browser/comments.pt
index 9521c57..974d8e8 100644
--- a/plone/app/discussion/browser/comments.pt
+++ b/plone/app/discussion/browser/comments.pt
@@ -150,7 +150,7 @@
formatting.
-
+
diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py
index f01b28e..cad88c0 100644
--- a/plone/app/discussion/browser/comments.py
+++ b/plone/app/discussion/browser/comments.py
@@ -18,6 +18,7 @@ from zope.interface import Interface, implements
from zope.viewlet.interfaces import IViewlet
from z3c.form import form, field, button, interfaces, widget
+from z3c.form.interfaces import IFormLayer
from z3c.form.browser.textarea import TextAreaWidget
from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
@@ -187,25 +188,16 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
pass
-class CommentsViewlet(ViewletBase, layout.FormWrapper):
+class CommentsViewlet(ViewletBase):
form = CommentForm
-
index = ViewPageTemplateFile('comments.pt')
- def __init__(self, context, request, view, manager):
- super(CommentsViewlet, self).__init__(context, request, view, manager)
- if self.form is not None:
- self.form_instance = self.form(self.context.aq_inner, self.request)
- self.form_instance.__name__ = self.__name__
-
- self.portal_discussion = getToolByName(self.context, 'portal_discussion', None)
- self.portal_membership = getToolByName(self.context, 'portal_membership', None)
-
- def render_form(self):
- z2.switch_on(self, request_layer=self.request_layer)
- self.form.update(self.form_instance)
- return self.form.render(self.form_instance)
+ def update(self):
+ super(CommentsViewlet, self).update()
+ z2.switch_on(self, request_layer=IFormLayer)
+ self.form = CommentForm(aq_inner(self.context), self.request)
+ self.form.update()
# view methods
@@ -304,7 +296,8 @@ class CommentsViewlet(ViewletBase, layout.FormWrapper):
return settings.show_commenter_image
def is_anonymous(self):
- return self.portal_membership.isAnonymousUser()
+ portal_membership = getToolByName(self.context, 'portal_membership', None)
+ return portal_membership.isAnonymousUser()
def login_action(self):
return '%s/login_form?came_from=%s' % (self.navigation_root_url, url_quote(self.request.get('URL', '')),)
From b293d3aa503a897aaadfe7c1d1af6bc1b572f3c7 Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Tue, 16 Feb 2010 20:35:27 +0000
Subject: [PATCH 11/13] remove plone.z3cform <= 0.5.7 requirement from setup.py
since p.a.d. works now with the latest plone.z3cform.
svn path=/plone.app.discussion/branches/notification/; revision=34053
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 6b6eed3..0b18a43 100644
--- a/setup.py
+++ b/setup.py
@@ -29,7 +29,7 @@ setup(name='plone.app.discussion',
'plone.app.z3cform',
'plone.indexer',
'plone.registry',
- 'plone.z3cform<=0.5.7',
+ 'plone.z3cform',
'ZODB3',
'zope.interface',
'zope.component',
From ff0270dac746b251fe817ba856e2ab0f1f60c708 Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Wed, 3 Mar 2010 19:17:13 +0000
Subject: [PATCH 12/13] hide author_notification field if author notification
is disabled. save author notification setting properly when saving the form.
svn path=/plone.app.discussion/branches/notification/; revision=34475
---
plone/app/discussion/browser/comments.py | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py
index cad88c0..e73dfcd 100644
--- a/plone/app/discussion/browser/comments.py
+++ b/plone/app/discussion/browser/comments.py
@@ -76,11 +76,10 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
# Notification enabled
- # XXX: Not working! ComponentLookupError
- #registry = queryUtility(IRegistry)
- #settings = registry.forInterface(IDiscussionSettings)
- #if not settings.notification_enabled:
- # self.widgets['author_notification'].mode = interfaces.HIDDEN_MODE
+ registry = queryUtility(IRegistry)
+ settings = registry.forInterface(IDiscussionSettings)
+ if not settings.user_notification_enabled:
+ self.widgets['author_notification'].mode = interfaces.HIDDEN_MODE
def updateActions(self):
super(CommentForm, self).updateActions()
@@ -124,7 +123,9 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
author_username = data['author_username']
if 'author_email' in data:
author_email = data['author_email']
-
+ if 'author_notification' in data:
+ author_notification = data['author_notification']
+
# The add-comment view is called on the conversation object
conversation = IConversation(self.__parent__)
@@ -144,6 +145,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
comment.creator = author_name
comment.author_name = author_name
comment.author_email = author_email
+ comment.author_notification = author_notification
comment.creation_date = comment.modification_date = datetime.now()
else:
member = portal_membership.getAuthenticatedMember()
@@ -155,6 +157,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
comment.author_username = member.getUserName()
comment.author_name = member.getProperty('fullname')
comment.author_email = member.getProperty('email')
+ comment.author_notification = comment.author_notification
comment.creation_date = comment.modification_date = datetime.now()
# Check if the added comment is a reply to an existing comment
From d81b95f7c11b58039b20047defde0060a1a5dadd Mon Sep 17 00:00:00 2001
From: Timo Stollenwerk
Date: Wed, 3 Mar 2010 19:20:02 +0000
Subject: [PATCH 13/13] more catalog tests added.
svn path=/plone.app.discussion/branches/notification/; revision=34476
---
plone/app/discussion/tests/test_catalog.py | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/plone/app/discussion/tests/test_catalog.py b/plone/app/discussion/tests/test_catalog.py
index d8458a6..8f1d221 100644
--- a/plone/app/discussion/tests/test_catalog.py
+++ b/plone/app/discussion/tests/test_catalog.py
@@ -228,6 +228,22 @@ class CommentCatalogTest(PloneTestCase):
# object the comment was added to
self.assertEquals(self.comment_brain.in_response_to, 'doc1')
+ def test_add_comment(self):
+ self.failUnless(self.comment_brain)
+
+ def test_delete_comment(self):
+ # Make sure a comment is removed from the catalog as well when it is
+ # deleted.
+ del self.conversation[self.comment_id]
+ brains = self.catalog.searchResults(
+ path = {'query' : '/'.join(self.comment.getPhysicalPath()) })
+ self.assertEquals(len(brains), 0)
+
+ def test_remove_comments_when_content_object_is_removed(self):
+ # Make sure all comments are removed from the catalog, if the content
+ # object is removed.
+ pass
+
def test_clear_and_rebuild_catalog(self):
# Clear and rebuild catalog
self.catalog.clearFindAndRebuild()