provide "delete own comments" as a configurable option
This commit is contained in:
parent
e6172a219e
commit
82a473c138
@ -5,6 +5,9 @@ Changelog
|
|||||||
2.2.9 (unreleased)
|
2.2.9 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Provide 'delete own comments' as a configurable option
|
||||||
|
[gyst]
|
||||||
|
|
||||||
- Make comments editable.
|
- Make comments editable.
|
||||||
[pjstevns, gyst]
|
[pjstevns, gyst]
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@
|
|||||||
has_author_link python:author_home_url and not isAnon;
|
has_author_link python:author_home_url and not isAnon;
|
||||||
portrait_url python:view.get_commenter_portrait(reply.author_username);
|
portrait_url python:view.get_commenter_portrait(reply.author_username);
|
||||||
review_state python:wtool.getInfoFor(reply, 'review_state', 'none');
|
review_state python:wtool.getInfoFor(reply, 'review_state', 'none');
|
||||||
canEdit python:view.can_edit(reply)"
|
canEdit python:view.can_edit(reply);
|
||||||
|
canDelete python:view.can_delete(reply)"
|
||||||
tal:attributes="class python:'comment replyTreeLevel'+str(depth)+' state-'+str(review_state);
|
tal:attributes="class python:'comment replyTreeLevel'+str(depth)+' state-'+str(review_state);
|
||||||
id string:${reply/getId}"
|
id string:${reply/getId}"
|
||||||
tal:condition="python:canReview or review_state == 'published'">
|
tal:condition="python:canReview or review_state == 'published'">
|
||||||
@ -89,7 +90,7 @@
|
|||||||
action=""
|
action=""
|
||||||
method="post"
|
method="post"
|
||||||
class="commentactionsform"
|
class="commentactionsform"
|
||||||
tal:condition="python:canReview"
|
tal:condition="python:canDelete"
|
||||||
tal:attributes="action string:${reply/absolute_url}/@@moderate-delete-comment">
|
tal:attributes="action string:${reply/absolute_url}/@@moderate-delete-comment">
|
||||||
<input name="form.button.DeleteComment"
|
<input name="form.button.DeleteComment"
|
||||||
class="destructive"
|
class="destructive"
|
||||||
|
@ -299,12 +299,28 @@ class CommentsViewlet(ViewletBase):
|
|||||||
aq_inner(self.context))
|
aq_inner(self.context))
|
||||||
|
|
||||||
def can_edit(self, reply):
|
def can_edit(self, reply):
|
||||||
"""Returns true if current user has the 'Delete objects'
|
"""Returns true if current user has the 'Edit comments'
|
||||||
permission.
|
permission.
|
||||||
"""
|
"""
|
||||||
return getSecurityManager().checkPermission('Edit comments',
|
return getSecurityManager().checkPermission('Edit comments',
|
||||||
aq_inner(reply))
|
aq_inner(reply))
|
||||||
|
|
||||||
|
def can_delete(self, reply):
|
||||||
|
"""By default requires 'Review comments'.
|
||||||
|
If 'delete own comments' is enabled, requires 'Edit comments'.
|
||||||
|
"""
|
||||||
|
if self.is_delete_own_comment_allowed():
|
||||||
|
permission = 'Edit comments'
|
||||||
|
else:
|
||||||
|
permission = 'Review comments'
|
||||||
|
return getSecurityManager().checkPermission(permission,
|
||||||
|
aq_inner(reply))
|
||||||
|
|
||||||
|
def is_delete_own_comment_allowed(self):
|
||||||
|
registry = queryUtility(IRegistry)
|
||||||
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
|
return settings.delete_own_comment_enabled
|
||||||
|
|
||||||
def is_discussion_allowed(self):
|
def is_discussion_allowed(self):
|
||||||
context = aq_inner(self.context)
|
context = aq_inner(self.context)
|
||||||
return context.restrictedTraverse('@@conversation_view').enabled()
|
return context.restrictedTraverse('@@conversation_view').enabled()
|
||||||
|
@ -80,13 +80,15 @@
|
|||||||
permission="plone.app.discussion.EditComments"
|
permission="plone.app.discussion.EditComments"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Delete comment view -->
|
<!-- Delete comment view
|
||||||
|
has conditional security dependent on controlpanel settings.
|
||||||
|
-->
|
||||||
<browser:page
|
<browser:page
|
||||||
for="plone.app.discussion.interfaces.IComment"
|
for="plone.app.discussion.interfaces.IComment"
|
||||||
name="moderate-delete-comment"
|
name="moderate-delete-comment"
|
||||||
layer="..interfaces.IDiscussionLayer"
|
layer="..interfaces.IDiscussionLayer"
|
||||||
class=".moderation.DeleteComment"
|
class=".moderation.DeleteComment"
|
||||||
permission="plone.app.discussion.ReviewComments"
|
permission="zope2.DeleteObjects"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Publish comment view -->
|
<!-- Publish comment view -->
|
||||||
|
@ -54,6 +54,8 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
|||||||
SingleCheckBoxFieldWidget
|
SingleCheckBoxFieldWidget
|
||||||
self.fields['edit_comment_enabled'].widgetFactory = \
|
self.fields['edit_comment_enabled'].widgetFactory = \
|
||||||
SingleCheckBoxFieldWidget
|
SingleCheckBoxFieldWidget
|
||||||
|
self.fields['delete_own_comment_enabled'].widgetFactory = \
|
||||||
|
SingleCheckBoxFieldWidget
|
||||||
self.fields['anonymous_comments'].widgetFactory = \
|
self.fields['anonymous_comments'].widgetFactory = \
|
||||||
SingleCheckBoxFieldWidget
|
SingleCheckBoxFieldWidget
|
||||||
self.fields['show_commenter_image'].widgetFactory = \
|
self.fields['show_commenter_image'].widgetFactory = \
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from Acquisition import aq_inner, aq_parent
|
from Acquisition import aq_inner, aq_parent
|
||||||
|
from AccessControl import getSecurityManager
|
||||||
|
from zope.component import queryUtility
|
||||||
|
|
||||||
from Products.Five.browser import BrowserView
|
from Products.Five.browser import BrowserView
|
||||||
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
|
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
|
||||||
@ -8,6 +10,8 @@ from Products.CMFCore.utils import getToolByName
|
|||||||
|
|
||||||
from Products.statusmessages.interfaces import IStatusMessage
|
from Products.statusmessages.interfaces import IStatusMessage
|
||||||
|
|
||||||
|
from plone.registry.interfaces import IRegistry
|
||||||
|
from plone.app.discussion.interfaces import IDiscussionSettings
|
||||||
from plone.app.discussion.interfaces import _
|
from plone.app.discussion.interfaces import _
|
||||||
from plone.app.discussion.interfaces import IComment
|
from plone.app.discussion.interfaces import IComment
|
||||||
|
|
||||||
@ -94,17 +98,37 @@ class DeleteComment(BrowserView):
|
|||||||
comment = aq_inner(self.context)
|
comment = aq_inner(self.context)
|
||||||
conversation = aq_parent(comment)
|
conversation = aq_parent(comment)
|
||||||
content_object = aq_parent(conversation)
|
content_object = aq_parent(conversation)
|
||||||
del conversation[comment.id]
|
# conditional security
|
||||||
content_object.reindexObject()
|
# base ZCML condition zope2.deleteObject allows 'delete own object'
|
||||||
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
# modify this for 'delete_own_comment_allowed' controlpanel setting
|
||||||
_("Comment deleted."),
|
if self.can_delete(comment):
|
||||||
type="info")
|
del conversation[comment.id]
|
||||||
|
content_object.reindexObject()
|
||||||
|
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
||||||
|
_("Comment deleted."),
|
||||||
|
type="info")
|
||||||
came_from = self.context.REQUEST.HTTP_REFERER
|
came_from = self.context.REQUEST.HTTP_REFERER
|
||||||
# if the referrer already has a came_from in it, don't redirect back
|
# if the referrer already has a came_from in it, don't redirect back
|
||||||
if len(came_from) == 0 or 'came_from=' in came_from:
|
if len(came_from) == 0 or 'came_from=' in came_from:
|
||||||
came_from = content_object.absolute_url()
|
came_from = content_object.absolute_url()
|
||||||
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
||||||
|
|
||||||
|
def can_delete(self, reply):
|
||||||
|
"""By default requires 'Review comments'.
|
||||||
|
If 'delete own comments' is enabled, requires 'Edit comments'.
|
||||||
|
"""
|
||||||
|
if self.is_delete_own_comment_allowed():
|
||||||
|
permission = 'Edit comments'
|
||||||
|
else:
|
||||||
|
permission = 'Review comments'
|
||||||
|
return getSecurityManager().checkPermission(permission,
|
||||||
|
aq_inner(reply))
|
||||||
|
|
||||||
|
def is_delete_own_comment_allowed(self):
|
||||||
|
registry = queryUtility(IRegistry)
|
||||||
|
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||||
|
return settings.delete_own_comment_enabled
|
||||||
|
|
||||||
|
|
||||||
class PublishComment(BrowserView):
|
class PublishComment(BrowserView):
|
||||||
"""Publish a comment.
|
"""Publish a comment.
|
||||||
|
@ -53,6 +53,16 @@
|
|||||||
profile="plone.app.discussion:default"
|
profile="plone.app.discussion:default"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<genericsetup:upgradeStep
|
||||||
|
title="delete own comments"
|
||||||
|
description="reload registry config to enable new field delete_own_comment_enabled"
|
||||||
|
source="101"
|
||||||
|
destination="102"
|
||||||
|
handler=".upgrades.update_registry"
|
||||||
|
sortkey="1"
|
||||||
|
profile="plone.app.discussion:default"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
<!-- Monkey Patches -->
|
<!-- Monkey Patches -->
|
||||||
|
|
||||||
|
@ -285,6 +285,17 @@ class IDiscussionSettings(Interface):
|
|||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
delete_own_comment_enabled = schema.Bool(
|
||||||
|
title=_(u"label_delete_own_comment_enabled",
|
||||||
|
default="Allow users to delete their own comment threads"),
|
||||||
|
description=_(u"help_edit_comment_enabled",
|
||||||
|
default=u"If selected, users may delete their own "
|
||||||
|
"comments -> AND the whole reply thread below that "
|
||||||
|
"comment!"),
|
||||||
|
required=False,
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
text_transform = schema.Choice(
|
text_transform = schema.Choice(
|
||||||
title=_(u"label_text_transform",
|
title=_(u"label_text_transform",
|
||||||
default="Comment text transform"),
|
default="Comment text transform"),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<metadata>
|
<metadata>
|
||||||
<version>101</version>
|
<version>102</version>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>profile-plone.app.registry:default</dependency>
|
<dependency>profile-plone.app.registry:default</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -320,6 +320,146 @@ But Anon can see the edited comment.
|
|||||||
True
|
True
|
||||||
|
|
||||||
|
|
||||||
|
Deleting existing comments | 'delete own comments' disabled
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
Anonymous cannot delete comments
|
||||||
|
|
||||||
|
>>> unprivileged_browser.open(urldoc1)
|
||||||
|
>>> 'form.button.Delete' in unprivileged_browser.contents
|
||||||
|
False
|
||||||
|
|
||||||
|
A member cannot delete his own comments, unless this is explicitly enabled (see later)
|
||||||
|
|
||||||
|
>>> browser_member.open(urldoc1)
|
||||||
|
>>> 'form.button.Delete' in browser_member.contents
|
||||||
|
False
|
||||||
|
|
||||||
|
Admin can delete comments
|
||||||
|
|
||||||
|
>>> browser.open(urldoc1)
|
||||||
|
>>> 'form.button.Delete' in browser.contents
|
||||||
|
True
|
||||||
|
|
||||||
|
Extract the delete comment url from the first "delete comment" button
|
||||||
|
|
||||||
|
>>> browser.open(urldoc1)
|
||||||
|
>>> form = browser.getForm(name='delete', index=0)
|
||||||
|
>>> delete_url = form.action
|
||||||
|
>>> '@@moderate-delete-comment' in delete_url
|
||||||
|
True
|
||||||
|
>>> comment_id = delete_url.split('/')[-2]
|
||||||
|
|
||||||
|
Anonymous cannot delete a comment by hitting the delete url directly.
|
||||||
|
|
||||||
|
>>> unprivileged_browser.open(delete_url)
|
||||||
|
|
||||||
|
The comment is still there
|
||||||
|
|
||||||
|
>>> unprivileged_browser.open(urldoc1)
|
||||||
|
>>> comment_id in unprivileged_browser.contents
|
||||||
|
True
|
||||||
|
|
||||||
|
|
||||||
|
A Member cannot delete even his own comment by hitting the delete url directly.
|
||||||
|
|
||||||
|
Extract the member comment id from the admin browser
|
||||||
|
|
||||||
|
>>> form = browser.getForm(name='delete', index=2)
|
||||||
|
>>> delete_url = form.action
|
||||||
|
>>> '@@moderate-delete-comment' in delete_url
|
||||||
|
True
|
||||||
|
>>> comment_id = delete_url.split('/')[-2]
|
||||||
|
|
||||||
|
Now try to hit that url as the member owning that comment.
|
||||||
|
Work around some possible testbrowser breakage and check the result later.
|
||||||
|
|
||||||
|
>>> try:
|
||||||
|
... browser_member.open(delete_url)
|
||||||
|
... except:
|
||||||
|
... pass
|
||||||
|
|
||||||
|
The comment is still there
|
||||||
|
|
||||||
|
>>> browser_member.open(urldoc1)
|
||||||
|
>>> comment_id in browser_member.contents
|
||||||
|
True
|
||||||
|
>>> 'Comment from Jim' in browser_member.contents
|
||||||
|
True
|
||||||
|
|
||||||
|
Admin, who hase 'review comments' permission, can delete comments
|
||||||
|
|
||||||
|
>>> browser.open(urldoc1)
|
||||||
|
>>> form = browser.getForm(name='delete', index=0)
|
||||||
|
>>> '@@moderate-delete-comment' in form.action
|
||||||
|
True
|
||||||
|
|
||||||
|
>>> comment_id = form.action.split('/')[-2]
|
||||||
|
|
||||||
|
Submitting the form runs into a testbrowser notFoundException.
|
||||||
|
We'll just catch that and check the result later.
|
||||||
|
|
||||||
|
>>> try:
|
||||||
|
... form.submit()
|
||||||
|
... except:
|
||||||
|
... pass
|
||||||
|
|
||||||
|
Returning to the document we find the deleted comment is indeed gone
|
||||||
|
|
||||||
|
>>> browser.open(urldoc1)
|
||||||
|
>>> comment_id in browser.contents
|
||||||
|
False
|
||||||
|
|
||||||
|
|
||||||
|
Deleting existing comments | 'delete own comments' ENABLED
|
||||||
|
----------------------------------------------------------
|
||||||
|
|
||||||
|
Enable deletion of own comments
|
||||||
|
|
||||||
|
>>> from zope.component import queryUtility
|
||||||
|
>>> from plone.registry.interfaces import IRegistry
|
||||||
|
>>> from plone.app.discussion.interfaces import IDiscussionSettings
|
||||||
|
>>> registry = queryUtility(IRegistry)
|
||||||
|
>>> settings = registry.forInterface(IDiscussionSettings)
|
||||||
|
>>> settings.delete_own_comment_enabled = True
|
||||||
|
|
||||||
|
>>> import transaction
|
||||||
|
>>> transaction.commit()
|
||||||
|
|
||||||
|
Anonymous still cannot delete comments
|
||||||
|
|
||||||
|
>>> unprivileged_browser.open(urldoc1)
|
||||||
|
>>> 'form.button.Delete' in unprivileged_browser.contents
|
||||||
|
False
|
||||||
|
|
||||||
|
A member can now delete his own comments
|
||||||
|
|
||||||
|
>>> browser_member.open(urldoc1)
|
||||||
|
>>> 'form.button.Delete' in browser_member.contents
|
||||||
|
True
|
||||||
|
|
||||||
|
>>> form = browser_member.getForm(name='delete', index=0)
|
||||||
|
>>> '@@moderate-delete-comment' in form.action
|
||||||
|
True
|
||||||
|
|
||||||
|
>>> comment_id = form.action.split('/')[-2]
|
||||||
|
|
||||||
|
Submitting the form runs into a testbrowser notFoundException.
|
||||||
|
We'll just catch that and check the result later.
|
||||||
|
|
||||||
|
>>> try:
|
||||||
|
... form.submit()
|
||||||
|
... except:
|
||||||
|
... pass
|
||||||
|
|
||||||
|
Returning to the document we find the deleted comment is indeed gone
|
||||||
|
|
||||||
|
>>> browser_member.open(urldoc1)
|
||||||
|
>>> comment_id in browser_member.contents
|
||||||
|
False
|
||||||
|
>>> 'Comment from Jim' in browser_member.contents
|
||||||
|
False
|
||||||
|
|
||||||
|
|
||||||
Post a comment with comment review workflow enabled
|
Post a comment with comment review workflow enabled
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user