provide "delete own comments" as a configurable option

This commit is contained in:
Guido A.J. Stevens 2013-09-19 08:39:52 +00:00
parent e6172a219e
commit 82a473c138
10 changed files with 220 additions and 11 deletions

View File

@ -5,6 +5,9 @@ Changelog
2.2.9 (unreleased)
------------------
- Provide 'delete own comments' as a configurable option
[gyst]
- Make comments editable.
[pjstevns, gyst]

View File

@ -36,7 +36,8 @@
has_author_link python:author_home_url and not isAnon;
portrait_url python:view.get_commenter_portrait(reply.author_username);
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);
id string:${reply/getId}"
tal:condition="python:canReview or review_state == 'published'">
@ -89,7 +90,7 @@
action=""
method="post"
class="commentactionsform"
tal:condition="python:canReview"
tal:condition="python:canDelete"
tal:attributes="action string:${reply/absolute_url}/@@moderate-delete-comment">
<input name="form.button.DeleteComment"
class="destructive"

View File

@ -299,12 +299,28 @@ class CommentsViewlet(ViewletBase):
aq_inner(self.context))
def can_edit(self, reply):
"""Returns true if current user has the 'Delete objects'
"""Returns true if current user has the 'Edit comments'
permission.
"""
return getSecurityManager().checkPermission('Edit comments',
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):
context = aq_inner(self.context)
return context.restrictedTraverse('@@conversation_view').enabled()

View File

@ -80,13 +80,15 @@
permission="plone.app.discussion.EditComments"
/>
<!-- Delete comment view -->
<!-- Delete comment view
has conditional security dependent on controlpanel settings.
-->
<browser:page
for="plone.app.discussion.interfaces.IComment"
name="moderate-delete-comment"
layer="..interfaces.IDiscussionLayer"
class=".moderation.DeleteComment"
permission="plone.app.discussion.ReviewComments"
permission="zope2.DeleteObjects"
/>
<!-- Publish comment view -->

View File

@ -54,6 +54,8 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
SingleCheckBoxFieldWidget
self.fields['edit_comment_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['delete_own_comment_enabled'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['anonymous_comments'].widgetFactory = \
SingleCheckBoxFieldWidget
self.fields['show_commenter_image'].widgetFactory = \

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
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.pagetemplatefile import ViewPageTemplateFile
@ -8,6 +10,8 @@ from Products.CMFCore.utils import getToolByName
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 IComment
@ -94,6 +98,10 @@ class DeleteComment(BrowserView):
comment = aq_inner(self.context)
conversation = aq_parent(comment)
content_object = aq_parent(conversation)
# conditional security
# base ZCML condition zope2.deleteObject allows 'delete own object'
# modify this for 'delete_own_comment_allowed' controlpanel setting
if self.can_delete(comment):
del conversation[comment.id]
content_object.reindexObject()
IStatusMessage(self.context.REQUEST).addStatusMessage(
@ -105,6 +113,22 @@ class DeleteComment(BrowserView):
came_from = content_object.absolute_url()
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):
"""Publish a comment.

View File

@ -53,6 +53,16 @@
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 -->

View File

@ -285,6 +285,17 @@ class IDiscussionSettings(Interface):
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(
title=_(u"label_text_transform",
default="Comment text transform"),

View File

@ -1,5 +1,5 @@
<metadata>
<version>101</version>
<version>102</version>
<dependencies>
<dependency>profile-plone.app.registry:default</dependency>
</dependencies>

View File

@ -320,6 +320,146 @@ But Anon can see the edited comment.
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
---------------------------------------------------