Add links to delete/approve a comment in the moderator notification email.
Remove the unnecessary workflow_action parameter from the PublishComments request. svn path=/plone.app.discussion/trunk/; revision=49045
This commit is contained in:
parent
521ea78ce3
commit
e75685d5c0
@ -4,6 +4,13 @@ Changelog
|
|||||||
2.0.1 (2011-04-22)
|
2.0.1 (2011-04-22)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Add links to delete/approve a comment in the moderator notification email.
|
||||||
|
[timo]
|
||||||
|
|
||||||
|
- Remove the unnecessary workflow_action parameter from the PublishComments
|
||||||
|
request.
|
||||||
|
[timo]
|
||||||
|
|
||||||
- Make sure the email settings in the control panel are disabled when commenting
|
- Make sure the email settings in the control panel are disabled when commenting
|
||||||
is disabled globally.
|
is disabled globally.
|
||||||
[timo]
|
[timo]
|
||||||
|
@ -57,7 +57,6 @@
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: target,
|
url: target,
|
||||||
data: "workflow_action=publish",
|
|
||||||
success: function (msg) {
|
success: function (msg) {
|
||||||
// fade out row
|
// fade out row
|
||||||
$(row).fadeOut("normal", function () {
|
$(row).fadeOut("normal", function () {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
from Acquisition import aq_inner, aq_parent
|
from Acquisition import aq_inner, aq_parent
|
||||||
|
|
||||||
from Products.Five.browser import BrowserView
|
from Products.Five.browser import BrowserView
|
||||||
@ -12,39 +13,34 @@ from plone.app.discussion.interfaces import IComment
|
|||||||
|
|
||||||
|
|
||||||
class View(BrowserView):
|
class View(BrowserView):
|
||||||
"""Main moderation View.
|
"""Comment moderation view.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
template = ViewPageTemplateFile('moderation.pt')
|
template = ViewPageTemplateFile('moderation.pt')
|
||||||
try:
|
try:
|
||||||
template.id = '@@moderate-comments'
|
template.id = '@@moderate-comments'
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# id is not writeable in Zope 2.12
|
# id is not writeable in Zope 2.12
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
# Hide the editable-object border
|
|
||||||
self.request.set('disable_border', True)
|
self.request.set('disable_border', True)
|
||||||
|
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
|
|
||||||
catalog = getToolByName(context, 'portal_catalog')
|
catalog = getToolByName(context, 'portal_catalog')
|
||||||
|
|
||||||
self.comments = catalog(object_provides=IComment.__identifier__,
|
self.comments = catalog(object_provides=IComment.__identifier__,
|
||||||
review_state='pending',
|
review_state='pending',
|
||||||
sort_on='created',
|
sort_on='created',
|
||||||
sort_order='reverse')
|
sort_order='reverse')
|
||||||
return self.template()
|
return self.template()
|
||||||
|
|
||||||
def moderation_enabled(self):
|
def moderation_enabled(self):
|
||||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||||
content type. A 'review workflow' is characterized by implementing
|
content type. A 'review workflow' is characterized by implementing
|
||||||
a 'pending' workflow state.
|
a 'pending' workflow state.
|
||||||
"""
|
"""
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
wf_tool = getToolByName(context, 'portal_workflow')
|
workflowTool = getToolByName(context, 'portal_workflow')
|
||||||
comment_workflow = wf_tool.getChainForPortalType('Discussion Item')[0]
|
comment_workflow = workflowTool.getChainForPortalType('Discussion Item')[0]
|
||||||
comment_workflow = wf_tool[comment_workflow]
|
comment_workflow = workflowTool[comment_workflow]
|
||||||
if 'pending' in comment_workflow.states:
|
if 'pending' in comment_workflow.states:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@ -59,9 +55,9 @@ class ModerateCommentsEnabled(BrowserView):
|
|||||||
a 'pending' workflow state.
|
a 'pending' workflow state.
|
||||||
"""
|
"""
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
wf_tool = getToolByName(context, 'portal_workflow', None)
|
workflowTool = getToolByName(context, 'portal_workflow', None)
|
||||||
comment_workflow = wf_tool.getChainForPortalType('Discussion Item')[0]
|
comment_workflow = workflowTool.getChainForPortalType('Discussion Item')[0]
|
||||||
comment_workflow = wf_tool[comment_workflow]
|
comment_workflow = workflowTool[comment_workflow]
|
||||||
if 'pending' in comment_workflow.states:
|
if 'pending' in comment_workflow.states:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@ -70,110 +66,103 @@ class ModerateCommentsEnabled(BrowserView):
|
|||||||
|
|
||||||
class DeleteComment(BrowserView):
|
class DeleteComment(BrowserView):
|
||||||
"""Delete a comment from a conversation.
|
"""Delete a comment from a conversation.
|
||||||
|
|
||||||
This view is always called directly on the comment object:
|
This view is always called directly on the comment object:
|
||||||
|
|
||||||
http://nohost/front-page/++conversation++default/1286289644723317/\
|
http://nohost/front-page/++conversation++default/1286289644723317/\
|
||||||
@@moderate-delete-comment
|
@@moderate-delete-comment
|
||||||
|
|
||||||
Each table row (comment) in the moderation view contains a hidden input
|
Each table row (comment) in the moderation view contains a hidden input
|
||||||
field with the absolute URL of the content object:
|
field with the absolute URL of the content object:
|
||||||
|
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
value="http://nohost/front-page/++conversation++default/\
|
value="http://nohost/front-page/++conversation++default/\
|
||||||
1286289644723317"
|
1286289644723317"
|
||||||
name="selected_obj_paths:list">
|
name="selected_obj_paths:list">
|
||||||
|
|
||||||
This absolute URL is called from a jQuery method that is bind to the
|
This absolute URL is called from a jQuery method that is bind to the
|
||||||
'delete' button of the table row. See javascripts/moderation.js for more
|
'delete' button of the table row. See javascripts/moderation.js for more
|
||||||
details.
|
details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
|
comment = aq_inner(self.context)
|
||||||
context = aq_inner(self.context)
|
conversation = aq_parent(comment)
|
||||||
comment_id = self.context.id
|
content_object = aq_parent(conversation)
|
||||||
|
del conversation[comment.id]
|
||||||
conversation = aq_parent(context)
|
|
||||||
|
|
||||||
del conversation[comment_id]
|
|
||||||
|
|
||||||
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
||||||
_("Comment deleted."),
|
_("Comment deleted."),
|
||||||
type="info")
|
type="info")
|
||||||
|
came_from = self.context.REQUEST.HTTP_REFERER
|
||||||
return self.context.REQUEST.RESPONSE.redirect(
|
if len(came_from) == 0:
|
||||||
self.context.REQUEST.HTTP_REFERER)
|
came_from = content_object.absolute_url()
|
||||||
|
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
||||||
|
|
||||||
|
|
||||||
class PublishComment(BrowserView):
|
class PublishComment(BrowserView):
|
||||||
"""Publish a comment.
|
"""Publish a comment.
|
||||||
|
|
||||||
This view is always called directly on the comment object:
|
This view is always called directly on the comment object:
|
||||||
|
|
||||||
http://nohost/front-page/++conversation++default/1286289644723317/\
|
http://nohost/front-page/++conversation++default/1286289644723317/\
|
||||||
@@moderate-publish-comment
|
@@moderate-publish-comment
|
||||||
|
|
||||||
Each table row (comment) in the moderation view contains a hidden input
|
Each table row (comment) in the moderation view contains a hidden input
|
||||||
field with the absolute URL of the content object:
|
field with the absolute URL of the content object:
|
||||||
|
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
value="http://nohost/front-page/++conversation++default/\
|
value="http://nohost/front-page/++conversation++default/\
|
||||||
1286289644723317"
|
1286289644723317"
|
||||||
name="selected_obj_paths:list">
|
name="selected_obj_paths:list">
|
||||||
|
|
||||||
This absolute URL is called from a jQuery method that is bind to the
|
This absolute URL is called from a jQuery method that is bind to the
|
||||||
'delete' button of the table row. See javascripts/moderation.js for more
|
'delete' button of the table row. See javascripts/moderation.js for more
|
||||||
details.
|
details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
|
|
||||||
comment = aq_inner(self.context)
|
comment = aq_inner(self.context)
|
||||||
workflow_action = self.request.form['workflow_action']
|
content_object = aq_parent(aq_parent(comment))
|
||||||
portal_workflow = getToolByName(comment, 'portal_workflow')
|
workflowTool = getToolByName(comment, 'portal_workflow', None)
|
||||||
portal_workflow.doActionFor(comment, workflow_action)
|
current_state = workflowTool.getInfoFor(comment, 'review_state')
|
||||||
|
if current_state != 'published':
|
||||||
catalog = getToolByName(comment, 'portal_catalog')
|
workflowTool.doActionFor(comment, 'publish')
|
||||||
catalog.reindexObject(comment)
|
catalogTool = getToolByName(comment, 'portal_catalog')
|
||||||
|
catalogTool.reindexObject(comment)
|
||||||
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
||||||
_("Comment approved."),
|
_("Comment approved."),
|
||||||
type="info")
|
type="info")
|
||||||
|
came_from = self.context.REQUEST.HTTP_REFERER
|
||||||
return self.context.REQUEST.RESPONSE.redirect(
|
if len(came_from) == 0:
|
||||||
self.context.REQUEST.HTTP_REFERER)
|
came_from = content_object.absolute_url()
|
||||||
|
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
||||||
|
|
||||||
class BulkActionsView(BrowserView):
|
class BulkActionsView(BrowserView):
|
||||||
"""Bulk actions (unapprove, approve, delete, mark as spam).
|
"""Bulk actions (unapprove, approve, delete, mark as spam).
|
||||||
|
|
||||||
Each table row of the moderation view has a checkbox with the absolute
|
Each table row of the moderation view has a checkbox with the absolute
|
||||||
path (without host and port) of the comment objects:
|
path (without host and port) of the comment objects:
|
||||||
|
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="paths:list"
|
name="paths:list"
|
||||||
value="/plone/front-page/++conversation++default/\
|
value="/plone/front-page/++conversation++default/\
|
||||||
1286289644723317"
|
1286289644723317"
|
||||||
id="cb_1286289644723317" />
|
id="cb_1286289644723317" />
|
||||||
|
|
||||||
If checked, the comment path will occur in the 'paths' variable of
|
If checked, the comment path will occur in the 'paths' variable of
|
||||||
the request when the bulk actions view is called. The bulk action
|
the request when the bulk actions view is called. The bulk action
|
||||||
(delete, publish, etc.) will be applied to all comments that are
|
(delete, publish, etc.) will be applied to all comments that are
|
||||||
included.
|
included.
|
||||||
|
|
||||||
The paths have to be 'traversable':
|
The paths have to be 'traversable':
|
||||||
|
|
||||||
/plone/front-page/++conversation++default/1286289644723317
|
/plone/front-page/++conversation++default/1286289644723317
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
|
|
||||||
if 'form.select.BulkAction' in self.request:
|
if 'form.select.BulkAction' in self.request:
|
||||||
|
|
||||||
bulkaction = self.request.get('form.select.BulkAction')
|
bulkaction = self.request.get('form.select.BulkAction')
|
||||||
|
|
||||||
self.paths = self.request.get('paths')
|
self.paths = self.request.get('paths')
|
||||||
if self.paths:
|
if self.paths:
|
||||||
if bulkaction == '-1':
|
if bulkaction == '-1':
|
||||||
@ -189,38 +178,38 @@ class BulkActionsView(BrowserView):
|
|||||||
self.delete()
|
self.delete()
|
||||||
else:
|
else:
|
||||||
raise KeyError # pragma: no cover
|
raise KeyError # pragma: no cover
|
||||||
|
|
||||||
def retract(self):
|
def retract(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def publish(self):
|
def publish(self):
|
||||||
"""Publishes all comments in the paths variable.
|
"""Publishes all comments in the paths variable.
|
||||||
|
|
||||||
Expects a list of absolute paths (without host and port):
|
Expects a list of absolute paths (without host and port):
|
||||||
|
|
||||||
/Plone/startseite/++conversation++default/1286200010610352
|
/Plone/startseite/++conversation++default/1286200010610352
|
||||||
|
|
||||||
"""
|
"""
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
for path in self.paths:
|
for path in self.paths:
|
||||||
comment = context.restrictedTraverse(path)
|
comment = context.restrictedTraverse(path)
|
||||||
portal_workflow = getToolByName(comment, 'portal_workflow')
|
workflowTool = getToolByName(comment, 'portal_workflow')
|
||||||
current_state = portal_workflow.getInfoFor(comment, 'review_state')
|
current_state = workflowTool.getInfoFor(comment, 'review_state')
|
||||||
if current_state != 'published':
|
if current_state != 'published':
|
||||||
portal_workflow.doActionFor(comment, 'publish')
|
workflowTool.doActionFor(comment, 'publish')
|
||||||
catalog = getToolByName(comment, 'portal_catalog')
|
catalog = getToolByName(comment, 'portal_catalog')
|
||||||
catalog.reindexObject(comment)
|
catalog.reindexObject(comment)
|
||||||
|
|
||||||
def mark_as_spam(self):
|
def mark_as_spam(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""Deletes all comments in the paths variable.
|
"""Deletes all comments in the paths variable.
|
||||||
|
|
||||||
Expects a list of absolute paths (without host and port):
|
Expects a list of absolute paths (without host and port):
|
||||||
|
|
||||||
/Plone/startseite/++conversation++default/1286200010610352
|
/Plone/startseite/++conversation++default/1286200010610352
|
||||||
|
|
||||||
"""
|
"""
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
for path in self.paths:
|
for path in self.paths:
|
||||||
|
@ -43,83 +43,95 @@ from Products.CMFCore.CMFCatalogAware import WorkflowAware
|
|||||||
from OFS.role import RoleManager
|
from OFS.role import RoleManager
|
||||||
|
|
||||||
|
|
||||||
COMMENT_TITLE = _(u"comment_title",
|
COMMENT_TITLE = _(
|
||||||
|
u"comment_title",
|
||||||
default=u"${creator} on ${content}")
|
default=u"${creator} on ${content}")
|
||||||
|
|
||||||
MAIL_NOTIFICATION_MESSAGE = _(u"mail_notification_message",
|
MAIL_NOTIFICATION_MESSAGE = _(
|
||||||
|
u"mail_notification_message",
|
||||||
default=u"A comment on '${title}' "
|
default=u"A comment on '${title}' "
|
||||||
"has been posted here: ${link}\n\n"
|
"has been posted here: ${link}\n\n"
|
||||||
"---\n\n"
|
"---\n"
|
||||||
"${text}"
|
"${text}\n"
|
||||||
"---\n")
|
"---\n")
|
||||||
|
|
||||||
|
MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
|
||||||
|
u"mail_notification_message_moderator",
|
||||||
|
default=u"A comment on '${title}' "
|
||||||
|
"has been posted here: ${link}\n\n"
|
||||||
|
"---\n"
|
||||||
|
"${text}\n"
|
||||||
|
"---\n\n"
|
||||||
|
"Approve comment:\n${link_approve}\n\n"
|
||||||
|
"Delete comment:\n${link_delete}\n")
|
||||||
|
|
||||||
logger = logging.getLogger("plone.app.discussion")
|
logger = logging.getLogger("plone.app.discussion")
|
||||||
|
|
||||||
|
|
||||||
class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
||||||
RoleManager, Owned, Implicit, Persistent):
|
RoleManager, Owned, Implicit, Persistent):
|
||||||
"""A comment.
|
"""A comment.
|
||||||
|
|
||||||
This object attempts to be as lightweight as possible. We implement a
|
This object attempts to be as lightweight as possible. We implement a
|
||||||
number of standard methods instead of subclassing, to have total control
|
number of standard methods instead of subclassing, to have total control
|
||||||
over what goes into the object.
|
over what goes into the object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
implements(IComment)
|
implements(IComment)
|
||||||
|
|
||||||
meta_type = portal_type = 'Discussion Item'
|
meta_type = portal_type = 'Discussion Item'
|
||||||
# This needs to be kept in sync with types/Discussion_Item.xml title
|
# This needs to be kept in sync with types/Discussion_Item.xml title
|
||||||
fti_title = 'Comment'
|
fti_title = 'Comment'
|
||||||
|
|
||||||
__parent__ = None
|
__parent__ = None
|
||||||
|
|
||||||
comment_id = None # long
|
comment_id = None # long
|
||||||
in_reply_to = None # long
|
in_reply_to = None # long
|
||||||
|
|
||||||
title = u""
|
title = u""
|
||||||
|
|
||||||
mime_type = None
|
mime_type = None
|
||||||
text = u""
|
text = u""
|
||||||
|
|
||||||
creator = None
|
creator = None
|
||||||
creation_date = None
|
creation_date = None
|
||||||
modification_date = None
|
modification_date = None
|
||||||
|
|
||||||
author_username = None
|
author_username = None
|
||||||
|
|
||||||
author_name = None
|
author_name = None
|
||||||
author_email = None
|
author_email = None
|
||||||
|
|
||||||
user_notification = None
|
user_notification = None
|
||||||
|
|
||||||
# Note: we want to use zope.component.createObject() to instantiate
|
# Note: we want to use zope.component.createObject() to instantiate
|
||||||
# comments as far as possible. comment_id and __parent__ are set via
|
# comments as far as possible. comment_id and __parent__ are set via
|
||||||
# IConversation.addComment().
|
# IConversation.addComment().
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.creation_date = self.modification_date = datetime.utcnow()
|
self.creation_date = self.modification_date = datetime.utcnow()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __name__(self):
|
def __name__(self):
|
||||||
return self.comment_id and unicode(self.comment_id) or None
|
return self.comment_id and unicode(self.comment_id) or None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
return self.comment_id and str(self.comment_id) or None
|
return self.comment_id and str(self.comment_id) or None
|
||||||
|
|
||||||
def getId(self):
|
def getId(self):
|
||||||
"""The id of the comment, as a string.
|
"""The id of the comment, as a string.
|
||||||
"""
|
"""
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
def getText(self, targetMimetype=None):
|
def getText(self, targetMimetype=None):
|
||||||
"""The body text of a comment.
|
"""The body text of a comment.
|
||||||
"""
|
"""
|
||||||
transforms = getToolByName(self, 'portal_transforms')
|
transforms = getToolByName(self, 'portal_transforms')
|
||||||
|
|
||||||
if targetMimetype is None:
|
if targetMimetype is None:
|
||||||
targetMimetype = 'text/x-html-safe'
|
targetMimetype = 'text/x-html-safe'
|
||||||
|
|
||||||
sourceMimetype = getattr(self, 'mime_type', None)
|
sourceMimetype = getattr(self, 'mime_type', None)
|
||||||
if sourceMimetype is None:
|
if sourceMimetype is None:
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
@ -132,7 +144,7 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
|||||||
text,
|
text,
|
||||||
context=self,
|
context=self,
|
||||||
mimetype=sourceMimetype).getData()
|
mimetype=sourceMimetype).getData()
|
||||||
|
|
||||||
def Title(self):
|
def Title(self):
|
||||||
"""The title of the comment.
|
"""The title of the comment.
|
||||||
"""
|
"""
|
||||||
@ -146,7 +158,7 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
|||||||
else:
|
else:
|
||||||
creator = self.creator
|
creator = self.creator
|
||||||
creator = creator
|
creator = creator
|
||||||
|
|
||||||
# Fetch the content object (the parent of the comment is the
|
# Fetch the content object (the parent of the comment is the
|
||||||
# conversation, the parent of the conversation is the content object).
|
# conversation, the parent of the conversation is the content object).
|
||||||
content = aq_base(self.__parent__.__parent__)
|
content = aq_base(self.__parent__.__parent__)
|
||||||
@ -155,30 +167,31 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
|
|||||||
mapping={'creator': creator,
|
mapping={'creator': creator,
|
||||||
'content': safe_unicode(content.Title())}))
|
'content': safe_unicode(content.Title())}))
|
||||||
return title
|
return title
|
||||||
|
|
||||||
def Creator(self):
|
def Creator(self):
|
||||||
"""The name of the person who wrote the comment.
|
"""The name of the person who wrote the comment.
|
||||||
"""
|
"""
|
||||||
return self.creator
|
return self.creator
|
||||||
|
|
||||||
def Type(self):
|
def Type(self):
|
||||||
"""The Discussion Item content type.
|
"""The Discussion Item content type.
|
||||||
"""
|
"""
|
||||||
return self.fti_title
|
return self.fti_title
|
||||||
|
|
||||||
# CMF's event handlers assume any IDynamicType has these :(
|
# CMF's event handlers assume any IDynamicType has these :(
|
||||||
|
|
||||||
def opaqueItems(self): # pragma: no cover
|
def opaqueItems(self): # pragma: no cover
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def opaqueIds(self): # pragma: no cover
|
def opaqueIds(self): # pragma: no cover
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def opaqueValues(self): # pragma: no cover
|
def opaqueValues(self): # pragma: no cover
|
||||||
return []
|
return []
|
||||||
|
|
||||||
CommentFactory = Factory(Comment)
|
CommentFactory = Factory(Comment)
|
||||||
|
|
||||||
|
|
||||||
def notify_workflow(obj, event):
|
def notify_workflow(obj, event):
|
||||||
"""Tell the workflow tool when a comment is added
|
"""Tell the workflow tool when a comment is added
|
||||||
"""
|
"""
|
||||||
@ -186,6 +199,7 @@ def notify_workflow(obj, event):
|
|||||||
if tool is not None:
|
if tool is not None:
|
||||||
tool.notifyCreated(obj)
|
tool.notifyCreated(obj)
|
||||||
|
|
||||||
|
|
||||||
def notify_content_object(obj, event):
|
def notify_content_object(obj, event):
|
||||||
"""Tell the content object when a comment is added
|
"""Tell the content object when a comment is added
|
||||||
"""
|
"""
|
||||||
@ -207,20 +221,20 @@ def notify_content_object_deleted(obj, event):
|
|||||||
|
|
||||||
def notify_user(obj, event):
|
def notify_user(obj, event):
|
||||||
"""Tell users when a comment has been added.
|
"""Tell users when a comment has been added.
|
||||||
|
|
||||||
This method composes and sends emails to all users that have added a
|
This method composes and sends emails to all users that have added a
|
||||||
comment to this conversation and enabled user notification.
|
comment to this conversation and enabled user notification.
|
||||||
|
|
||||||
This requires the user_notification setting to be enabled in the
|
This requires the user_notification setting to be enabled in the
|
||||||
discussion control panel.
|
discussion control panel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check if user notification is enabled
|
# Check if user notification is enabled
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
if not settings.user_notification_enabled:
|
if not settings.user_notification_enabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get informations that are necessary to send an email
|
# Get informations that are necessary to send an email
|
||||||
mail_host = getToolByName(obj, 'MailHost')
|
mail_host = getToolByName(obj, 'MailHost')
|
||||||
portal_url = getToolByName(obj, 'portal_url')
|
portal_url = getToolByName(obj, 'portal_url')
|
||||||
@ -273,24 +287,23 @@ def notify_user(obj, event):
|
|||||||
|
|
||||||
def notify_moderator(obj, event):
|
def notify_moderator(obj, event):
|
||||||
"""Tell the moderator when a comment needs attention.
|
"""Tell the moderator when a comment needs attention.
|
||||||
|
|
||||||
This method sends an email to the moderator if comment moderation is
|
This method sends an email to the moderator if comment moderation a new
|
||||||
enabled and a new comment has been added that needs to be approved.
|
comment has been added that needs to be approved.
|
||||||
|
|
||||||
|
The moderator_notification setting has to be enabled in the discussion
|
||||||
|
control panel.
|
||||||
|
|
||||||
Configure the moderator e-mail address in the discussion control panel.
|
Configure the moderator e-mail address in the discussion control panel.
|
||||||
If no moderator is configured but moderator notifications are turned on,
|
If no moderator is configured but moderator notifications are turned on,
|
||||||
the site admin email (from the mail control panel) will be used.
|
the site admin email (from the mail control panel) will be used.
|
||||||
|
|
||||||
This requires the moderator_notification to be enabled in the discussion
|
|
||||||
control panel and the comment_review_workflow enabled for the comment
|
|
||||||
content type.
|
|
||||||
"""
|
"""
|
||||||
# Check if moderator notification is enabled
|
# Check if moderator notification is enabled
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
if not settings.moderator_notification_enabled:
|
if not settings.moderator_notification_enabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get informations that are necessary to send an email
|
# Get informations that are necessary to send an email
|
||||||
mail_host = getToolByName(obj, 'MailHost')
|
mail_host = getToolByName(obj, 'MailHost')
|
||||||
portal_url = getToolByName(obj, 'portal_url')
|
portal_url = getToolByName(obj, 'portal_url')
|
||||||
@ -301,24 +314,26 @@ def notify_moderator(obj, event):
|
|||||||
mto = settings.moderator_email
|
mto = settings.moderator_email
|
||||||
else:
|
else:
|
||||||
mto = sender
|
mto = sender
|
||||||
|
|
||||||
# Check if a sender address is available
|
# Check if a sender address is available
|
||||||
if not sender:
|
if not sender:
|
||||||
return
|
return
|
||||||
|
|
||||||
conversation = aq_parent(obj)
|
conversation = aq_parent(obj)
|
||||||
content_object = aq_parent(conversation)
|
content_object = aq_parent(conversation)
|
||||||
|
|
||||||
# Compose email
|
# Compose email
|
||||||
#comment = conversation.getComments().next()
|
|
||||||
subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
|
subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
|
||||||
message = translate(Message(MAIL_NOTIFICATION_MESSAGE,
|
message = translate(Message(MAIL_NOTIFICATION_MESSAGE_MODERATOR,
|
||||||
mapping={'title': safe_unicode(content_object.title),
|
mapping={
|
||||||
'link': content_object.absolute_url() +
|
'title': safe_unicode(content_object.title),
|
||||||
'/view#' + obj.id,
|
'link': content_object.absolute_url() + '/view#' + obj.id,
|
||||||
'text': obj.text}),
|
'text': obj.text,
|
||||||
|
'link_approve': obj.absolute_url() + '/@@moderate-publish-comment',
|
||||||
|
'link_delete': obj.absolute_url() + '/@@moderate-delete-comment',
|
||||||
|
}),
|
||||||
context=obj.REQUEST)
|
context=obj.REQUEST)
|
||||||
|
|
||||||
# Send email
|
# Send email
|
||||||
try:
|
try:
|
||||||
mail_host.send(message, mto, sender, subject, charset='utf-8')
|
mail_host.send(message, mto, sender, subject, charset='utf-8')
|
||||||
|
@ -88,6 +88,8 @@ class TestUserNotificationUnit(unittest.TestCase):
|
|||||||
% comment_id
|
% comment_id
|
||||||
in msg)
|
in msg)
|
||||||
self.assertTrue('Comment text' in msg)
|
self.assertTrue('Comment text' in msg)
|
||||||
|
self.assertFalse('Approve comment' in msg)
|
||||||
|
self.assertFalse('Delete comment' in msg)
|
||||||
|
|
||||||
def test_do_not_notify_user_when_notification_is_disabled(self):
|
def test_do_not_notify_user_when_notification_is_disabled(self):
|
||||||
registry = queryUtility(IRegistry)
|
registry = queryUtility(IRegistry)
|
||||||
@ -222,6 +224,12 @@ class TestModeratorNotificationUnit(unittest.TestCase):
|
|||||||
% comment_id
|
% comment_id
|
||||||
in msg)
|
in msg)
|
||||||
self.assertTrue('Comment text' in msg)
|
self.assertTrue('Comment text' in msg)
|
||||||
|
self.assertTrue(
|
||||||
|
'Approve comment:\nhttp://nohost/plone/doc1/++conversation++default/%s/@@moderat=\ne-publish-comment'
|
||||||
|
% comment_id in msg)
|
||||||
|
self.assertTrue(
|
||||||
|
'Delete comment:\nhttp://nohost/plone/doc1/++conversation++default/%s/@@moderat=\ne-delete-comment'
|
||||||
|
% comment_id in msg)
|
||||||
|
|
||||||
def test_notify_moderator_specific_address(self):
|
def test_notify_moderator_specific_address(self):
|
||||||
# A moderator email address can be specified in the control panel.
|
# A moderator email address can be specified in the control panel.
|
||||||
|
Loading…
Reference in New Issue
Block a user