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
This commit is contained in:
Timo Stollenwerk 2010-02-11 21:05:28 +00:00
parent 67b5ff566e
commit f0fb6514c7
6 changed files with 208 additions and 11 deletions

View File

@ -29,6 +29,7 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
self.fields['globally_enabled'].widgetFactory = SingleCheckBoxFieldWidget self.fields['globally_enabled'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['anonymous_comments'].widgetFactory = SingleCheckBoxFieldWidget self.fields['anonymous_comments'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['show_commenter_image'].widgetFactory = SingleCheckBoxFieldWidget self.fields['show_commenter_image'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['moderator_notification_enabled'].widgetFactory = SingleCheckBoxFieldWidget
self.fields['notification_enabled'].widgetFactory = SingleCheckBoxFieldWidget self.fields['notification_enabled'].widgetFactory = SingleCheckBoxFieldWidget
def updateWidgets(self): def updateWidgets(self):
@ -36,6 +37,7 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
self.widgets['globally_enabled'].label = _(u"Enable Comments") self.widgets['globally_enabled'].label = _(u"Enable Comments")
self.widgets['anonymous_comments'].label = _(u"Anonymous Comments") self.widgets['anonymous_comments'].label = _(u"Anonymous Comments")
self.widgets['show_commenter_image'].label = _(u"Commenter Image") 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['notification_enabled'].label = _(u"Email Notification")

View File

@ -1,10 +1,14 @@
"""The default comment class and factory. """The default comment class and factory.
""" """
from datetime import datetime from datetime import datetime
from zope.interface import implements from zope.interface import implements
from zope.component.factory import Factory from zope.component.factory import Factory
from zope.component import queryUtility
from Acquisition import aq_parent, Implicit from Acquisition import aq_parent, Implicit
from AccessControl.Role import RoleManager from AccessControl.Role import RoleManager
from AccessControl.Owned import Owned from AccessControl.Owned import Owned
@ -15,7 +19,9 @@ from Products.CMFCore.utils import getToolByName
from OFS.Traversable import Traversable 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: try:
# Plone 4: # Plone 4:
@ -127,12 +133,15 @@ def notify_content_object(obj, event):
"""Tell the content object when a comment is added """Tell the content object when a comment is added
""" """
content_obj = aq_parent(aq_parent(obj)) 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): def notify_user(obj, event):
"""Tell the user when a comment is added """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') mail_host = getToolByName(obj, 'MailHost')
portal_url = getToolByName(obj, 'portal_url') portal_url = getToolByName(obj, 'portal_url')
@ -142,11 +151,46 @@ def notify_user(obj, event):
if not sender: if not sender:
return return
subject = "Is this you?" for comment in conversation.getComments():
message = "A presenter called %s was added here %s" % (obj.title, obj.absolute_url(),) 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)
matching_users = acl_users.searchUsers(fullname=obj.title) def notify_moderator(obj, index):
for user_info in matching_users: """Tell the moderator when a comment needs attention
email = user_info.get('email', None) """
if email is not None:
mail_host.secureSend(message, email, sender, subject) # 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)

View File

@ -55,6 +55,19 @@ class IDiscussionSettings(Interface):
required=False, required=False,
default=True) 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( notification_enabled = schema.Bool(
title=_(u"label_notification_enabled", title=_(u"label_notification_enabled",
default=u"Enable email notification"), default=u"Enable email notification"),

View File

@ -37,6 +37,12 @@
handler=".tool.unindex_object" handler=".tool.unindex_object"
/> />
<subscriber
for="plone.app.discussion.interfaces.IComment
zope.lifecycleevent.interfaces.IObjectAddedEvent"
handler=".comment.notify_moderator"
/>
</configure> </configure>
<configure zcml:condition="installed zope.app.container"> <configure zcml:condition="installed zope.app.container">
@ -71,6 +77,12 @@
handler=".tool.unindex_object" handler=".tool.unindex_object"
/> />
<subscriber
for="plone.app.discussion.interfaces.IComment
zope.app.container.interfaces.IObjectAddedEvent"
handler=".comment.notify_moderator"
/>
</configure> </configure>
</configure> </configure>

View File

@ -17,6 +17,8 @@ from plone.app.discussion.interfaces import IConversation, IComment
class MigrationTest(PloneTestCase): class MigrationTest(PloneTestCase):
layer = DiscussionLayer
def afterSetUp(self): def afterSetUp(self):
self.loginAsPortalOwner() self.loginAsPortalOwner()
typetool = self.portal.portal_types typetool = self.portal.portal_types

View File

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