New feature: As a logged-in user, I can enable/disable email notification of additional comments on this content object.

svn path=/plone.app.discussion/trunk/; revision=40949
This commit is contained in:
Timo Stollenwerk 2010-10-30 15:02:05 +00:00
parent 5fe339215b
commit 82afd3ef15
8 changed files with 172 additions and 175 deletions

View File

@ -4,6 +4,10 @@ Changelog
1.0b11 (unreleased)
-------------------
- New feature: As a logged-in user, I can enable/disable email notification of
additional comments on this content object.
[timo]
- Disable the plone.app.registry check on schema elements, so no error is
raised on upgrades. This fixes https://dev.plone.org/plone/ticket/11195.
[timo]

View File

@ -15,6 +15,7 @@ from zope.interface import alsoProvides
from z3c.form import form, field, button, interfaces
from z3c.form.interfaces import IFormLayer
from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.CMFCore.utils import getToolByName
@ -34,6 +35,7 @@ from plone.app.discussion.interfaces import ICaptcha
from plone.app.discussion.browser.validator import CaptchaValidator
from plone.z3cform import z2
from plone.z3cform.widget import SingleCheckBoxWidget
from plone.z3cform.fieldsets import extensible
# starting from 0.6.0 version plone.z3cform has IWrappedForm interface
@ -62,8 +64,8 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
def updateFields(self):
super(CommentForm, self).updateFields()
#self.fields['author_notification'].widgetFactory =
# SingleCheckBoxFieldWidget
self.fields['user_notification'].widgetFactory = \
SingleCheckBoxFieldWidget
def updateWidgets(self):
super(CommentForm, self).updateWidgets()
@ -71,7 +73,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
# Widgets
self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
self.widgets['text'].addClass("autoresize")
#self.widgets['author_notification'].label = _(u"")
self.widgets['user_notification'].label = _(u"")
# Anonymous / Logged-in
portal_membership = getToolByName(self.context, 'portal_membership')
@ -83,12 +85,13 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
# 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
# XXX: Author notification code
#registry = queryUtility(IRegistry)
#settings = registry.forInterface(IDiscussionSettings, check=False)
#if not settings.user_notification_enabled:
# self.widgets['author_notification'].mode = interfaces.HIDDEN_MODE
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
portal_membership = getToolByName(self.context, 'portal_membership')
if not settings.user_notification_enabled or portal_membership.isAnonymousUser():
self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE
def updateActions(self):
super(CommentForm, self).updateActions()
@ -105,11 +108,11 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
data, errors = self.extractData()
if errors:
return
text = u""
author_name = u""
author_email = u""
#author_notification = None
user_notification = None
# Captcha check for anonymous users (if Captcha is enabled and
# anonymous commenting is allowed)
@ -135,8 +138,8 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
author_name = data['author_name']
if 'author_email' in data:
author_email = data['author_email']
#if 'author_notification' in data:
# author_notification = data['author_notification']
if 'user_notification' in data:
user_notification = data['user_notification']
# The add-comment view is called on the conversation object
conversation = IConversation(self.__parent__)
@ -163,7 +166,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.user_notification = user_notification
comment.creation_date = comment.modification_date = datetime.utcnow()
elif not portal_membership.isAnonymousUser():
# Member
@ -182,7 +185,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
comment.author_username = username
comment.author_name = fullname
comment.author_email = email
#comment.author_notification = comment.author_notification
comment.user_notification = user_notification
comment.creation_date = comment.modification_date = datetime.utcnow()
else:
raise Unauthorized, "Anonymous user tries to post a comment, but \
@ -340,7 +343,7 @@ class CommentsViewlet(ViewletBase):
def anonymous_discussion_allowed(self):
# Check if anonymous comments are allowed in the registry
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings)
settings = registry.forInterface(IDiscussionSettings, check=False)
return settings.anonymous_comments
def show_commenter_image(self):

View File

@ -32,8 +32,8 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
SingleCheckBoxFieldWidget
self.fields['moderator_notification_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
#self.fields['user_notification_enabled'].widgetFactory = \
# SingleCheckBoxFieldWidget
self.fields['user_notification_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
def updateWidgets(self):
super(DiscussionSettingsEditForm, self).updateWidgets()
@ -42,8 +42,8 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
self.widgets['show_commenter_image'].label = _(u"Commenter Image")
self.widgets['moderator_notification_enabled'].label = \
_(u"Moderator Email Notification")
#self.widgets['user_notification_enabled'].label = \
# _(u"User Email Notification")
self.widgets['user_notification_enabled'].label = \
_(u"User Email Notification")
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):

View File

@ -49,7 +49,7 @@ COMMENT_TITLE = _(u"comment_title",
default=u"${creator} on ${content}")
MAIL_NOTIFICATION_MESSAGE = _(u"mail_notification_message",
default=u"A comment with the title '${title}' "
default=u"A comment on '${title}' "
"has been posted here: ${link}")
@ -87,7 +87,7 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
author_name = None
author_email = None
author_notification = None
user_notification = None
# Note: we want to use zope.component.createObject() to instantiate
# comments as far as possible. comment_id and __parent__ are set via
@ -180,9 +180,7 @@ def notify_content_object_deleted(obj, event):
for comment in conversation.getComments():
del conversation[comment.id]
# XXX: This method is not enabled yet. Remove "pragma: no cover" as soon as the
# commented out notify user code has been removed.
def notify_user(obj, event): # pragma: no cover
def notify_user(obj, event):
"""Tell users when a comment has been added.
This method composes and sends emails to all users that have added a
@ -209,17 +207,17 @@ def notify_user(obj, event): # pragma: no cover
return
# Compose and send emails to all users that have add a comment to this
# conversation and enabled author_notification.
# conversation and enabled user_notification.
conversation = aq_parent(obj)
content_object = aq_parent(conversation)
for comment in conversation.getComments():
if obj != comment and \
comment.author_notification and comment.author_email:
comment.user_notification and comment.author_email:
subject = translate(_(u"A comment has been posted."),
context=obj.REQUEST)
message = translate(Message(MAIL_NOTIFICATION_MESSAGE,
mapping={'title': obj.title,
mapping={'title': content_object.title,
'link': content_object.absolute_url()}),
context=obj.REQUEST)
mail_host.send(message, comment.author_email, sender, subject)
@ -267,7 +265,7 @@ def notify_moderator(obj, event):
#comment = conversation.getComments().next()
subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
message = translate(Message(MAIL_NOTIFICATION_MESSAGE,
mapping={'title': obj.title,
mapping={'title': content_object.title,
'link': content_object.absolute_url()}),
context=obj.REQUEST)

View File

@ -71,13 +71,12 @@ class IDiscussionSettings(Interface):
title=_(u"label_show_commenter_image",
default=u"Show commenter image"),
description=_(u"help_show_commenter_image",
default=u"If selected, an image "
"of the user is shown "
"next to the comment."),
default=u"If selected, an image of the user is shown next to "
"the comment."),
required=False,
default=True,
)
moderator_notification_enabled = schema.Bool(
title=_(u"label_moderator_notification_enabled",
default=u"Enable moderator email notification"),
@ -88,16 +87,14 @@ class IDiscussionSettings(Interface):
default=False,
)
#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)
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):
@ -269,9 +266,9 @@ class IComment(Interface):
text = schema.Text(title=_(u"label_comment",
default=u"Comment"))
#author_notification = schema.Bool(title=_(u"Notify me of new comments via "
# "email."),
# required=False)
user_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"))

View File

@ -7,13 +7,11 @@
<configure zcml:condition="not-installed zope.app.container">
<!--
<subscriber
for="plone.app.discussion.interfaces.IComment
zope.lifecycleevent.interfaces.IObjectAddedEvent"
handler=".comment.notify_user"
/>
-->
<subscriber
for="plone.app.discussion.interfaces.IComment
@ -28,13 +26,11 @@
<configure zcml:condition="installed zope.app.container">
<!--
<subscriber
for="plone.app.discussion.interfaces.IComment
zope.app.container.interfaces.IObjectAddedEvent"
handler=".comment.notify_user"
/>
-->
<subscriber
for="plone.app.discussion.interfaces.IComment

View File

@ -14,129 +14,129 @@ from Products.CMFPlone.tests.utils import MockMailHost
from plone.registry.interfaces import IRegistry
from plone.app.discussion.interfaces import IConversation
from plone.app.discussion.interfaces import IDiscussionSettings
from plone.app.discussion.tests.layer import DiscussionLayer
#class TestUserNotificationUnit(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 user notification setting
# registry = queryUtility(IRegistry)
# settings = registry.forInterface(IDiscussionSettings)
# registry['plone.app.discussion.interfaces.IDiscussionSettings.\
# user_notification_enabled'] = True
#
# # Create test content
# 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_user(self):
# # Add a comment with user notification enabled. Add another comment
# # and make sure an email is send to the user of the first comment.
# 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), 1)
# self.failUnless(self.mailhost.messages[0])
# msg = self.mailhost.messages[0]
# self.failUnless('To: john@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
class TestUserNotificationUnit(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 user notification setting
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings)
registry['plone.app.discussion.interfaces.IDiscussionSettings' +
'.user_notification_enabled'] = True
# Create test content
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_user(self):
# Add a comment with user notification enabled. Add another comment
# and make sure an email is send to the user of the first comment.
comment = createObject('plone.Comment')
comment.title = 'Comment 1'
comment.text = 'Comment text'
comment.user_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), 1)
self.failUnless(self.mailhost.messages[0])
msg = self.mailhost.messages[0]
self.failUnless('To: john@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_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)
#
# def test_do_not_notify_user_when_no_sender_is_available(self):
# # Set sender mail address to none 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)
# (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.user_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.user_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)
def test_do_not_notify_user_when_no_sender_is_available(self):
# Set sender mail address to none 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.user_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):

View File

@ -3,13 +3,13 @@ Products.PDBDebugMode = 1.1
Products.PrintingMailHost = 0.7
Sphinx-PyPI-upload = 0.2.1
collective.recipe.omelette = 0.9
collective.recipe.sphinxbuilder = 0.6.3.3
collective.recipe.template = 1.8
collective.xmltestreport = 1.0b3
collective.z3cform.datetimewidget = 1.0.2
coverage = 3.4
hexagonit.recipe.download = 1.4.1
interlude = 1.0
ipython = 0.10
ipython = 0.10.1
logilab.pylintinstaller = 0.15.2
mr.developer = 1.16
plone.app.testing = 1.0a2
@ -18,24 +18,23 @@ plone.supermodel = 1.0b5
plone.testing = 1.0a2
recaptcha-client = 1.0.5
repoze.sphinx.autointerface = 0.4
zc.recipe.cmmi = 1.3.2
zest.releaser = 3.15
zptlint = 0.2.3
#Required by:
#collective.akismet 1.0a3
#collective.akismet 1.0b2dev
akismet = 0.2.0
#Required by:
#plone.app.discussion 1.0b9dev
#plone.app.discussion 1.0b11
collective.autopermission = 1.0b1
#Required by:
#plone.app.discussion 1.0b9dev
#plone.app.discussion 1.0b11
plone.app.registry = 1.0b2
#Required by:
#plone.app.discussion 1.0b9dev
#plone.app.discussion 1.0b11
plone.registry = 1.0b2
#Required by: