enabled property code added to conversation.

svn path=/plone.app.discussion/trunk/; revision=27380
This commit is contained in:
Timo Stollenwerk 2009-06-11 10:14:44 +00:00
parent 0f7d05566a
commit e465d73c95
2 changed files with 136 additions and 21 deletions

View File

@ -14,14 +14,18 @@ import time
from persistent import Persistent
from plone.registry.interfaces import IRegistry
from zope.app.component.hooks import getSite
from zope.interface import implements, implementer
from zope.component import adapts, adapter
from zope.component import adapts, adapter, queryUtility
from zope.annotation.interfaces import IAnnotations, IAnnotatable
from zope.event import notify
from Acquisition import aq_base
from Acquisition import aq_base, aq_inner
from Acquisition import Explicit
from OFS.Traversable import Traversable
@ -29,6 +33,8 @@ from OFS.Traversable import Traversable
from OFS.event import ObjectWillBeAddedEvent
from OFS.event import ObjectWillBeRemovedEvent
from Products.CMFCore.utils import getToolByName
from zope.app.container.contained import ContainerModifiedEvent
from zope.app.container.contained import ObjectAddedEvent
@ -44,7 +50,7 @@ except ImportError:
from BTrees.OOBTree import OOBTree as LOBTree
from BTrees.OOBTree import OOSet as LLSet
from plone.app.discussion.interfaces import IConversation, IReplies
from plone.app.discussion.interfaces import IConversation, IReplies, IDiscussionSettings
from plone.app.discussion.comment import Comment
ANNOTATION_KEY = 'plone.app.discussion:conversation'
@ -80,7 +86,29 @@ class Conversation(Traversable, Persistent, Explicit):
@property
def enabled(self):
# TODO - check __parent__'s settings + global settings
# Returns True if discussion is allowed
# Fetch discussion registry
registry = queryUtility(IRegistry)
settings = registry.for_interface(IDiscussionSettings)
# Check if discussion is allowed globally
if not settings.globally_enabled:
return False
# Check if discussion is allowed on the content type
site = getSite()
portal_types = getToolByName(site, 'portal_types')
portal_type = self.__parent__.portal_type
document_fti = getattr(portal_types, 'Document')
if not document_fti.getProperty('allow_discussion'):
# If discussion is not allowed on the content type,
# check if 'allow discussion' is overridden on the content object.
#if hasattr( aq_base(self.__parent__), 'allow_discussion' ):
portal_discussion = getToolByName(site, 'portal_discussion')
if not portal_discussion.isDiscussionAllowedFor(aq_inner(self.__parent__)):
return False
return True
@property
@ -104,7 +132,7 @@ class Conversation(Traversable, Persistent, Explicit):
count = 0l
for comment in self._comments.values(min=start):
yield comment
count += 1
if size and count > size:
return
@ -112,11 +140,11 @@ class Conversation(Traversable, Persistent, Explicit):
def getThreads(self, start=0, size=None, root=0, depth=None):
"""Get threaded comments
"""
def recurse(comment_id, d=0):
# Yield the current comment before we look for its children
yield {'id': comment_id, 'comment': self._comments[comment_id], 'depth': d}
# Recurse if there are children and we are not out of our depth
if depth is None or d + 1 < depth:
children = self._children.get(comment_id, None)
@ -124,18 +152,18 @@ class Conversation(Traversable, Persistent, Explicit):
for child_id in children:
for value in recurse(child_id, d+1):
yield value
# Find top level threads
comments = self._children.get(root, None)
if comments is not None:
count = 0l
for comment_id in comments.keys(min=start):
# Abort if we have found all the threads we want
count += 1
if size and count > size:
return
# Let the closure recurse
for value in recurse(comment_id):
yield value
@ -211,14 +239,14 @@ class Conversation(Traversable, Persistent, Explicit):
for child_id in self._children.get(key, []):
# avoid sending ContainerModifiedEvent multiple times
self.__delitem__(child_id, suppress_container_modified=True)
# XXX: During the events sent from the recursive deletion, the
# _children data structure may be in an inconsistent state. We may
# need to delay sending the events until it is fixed up.
# Remove the comment from _comments
self._comments.pop(key)
# Remove this comment as a child of its parent
if not suppress_container_modified:
parent = comment.in_reply_to
@ -226,16 +254,16 @@ class Conversation(Traversable, Persistent, Explicit):
parent_children = self._children.get(parent, None)
if parent_children is not None and key in parent_children:
parent_children.remove(key)
# Remove commentators
if commentator and commentator in self._commentators:
if self._commentators[commentator] <= 1:
del self._commentators[commentator]
else:
self._commentators[commentator] -= 1
notify(ObjectRemovedEvent(comment, self, key))
if not suppress_container_modified:
notify(ContainerModifiedEvent(self))
@ -279,6 +307,7 @@ def conversationAdapterFactory(content):
conversation = Conversation()
annotions[ANNOTATION_KEY] = conversation
conversation = annotions[ANNOTATION_KEY]
conversation.__parent__ = aq_base(content)
return conversation
class ConversationReplies(object):
@ -350,13 +379,13 @@ class ConversationReplies(object):
def iteritems(self):
for key in self.children:
yield (key, self.conversation[key],)
@property
def children(self):
# we need to look this up every time, because we may not have a
# dict yet when the adapter is first created
return self.conversation._children.get(self.comment_id, LLSet())
class CommentReplies(ConversationReplies):
"""An IReplies adapter for comments.

View File

@ -1,14 +1,14 @@
import unittest
from datetime import datetime, timedelta
from plone.registry import Registry
from zope.component import createObject
from zope.component import createObject, queryUtility
from Acquisition import aq_base, aq_parent, aq_inner
from plone.app.vocabularies.types import BAD_TYPES
from plone.registry.interfaces import IRegistry
from Products.CMFCore.utils import getToolByName
from Products.PloneTestCase.ptc import PloneTestCase
from plone.app.discussion.tests.layer import DiscussionLayer
@ -155,6 +155,82 @@ class ConversationTest(PloneTestCase):
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
], list(conversation.getThreads()))
def test_disable_commenting_globally(self):
# Create a conversation.
conversation = IConversation(self.portal.doc1)
# We have to allow discussion on Document content type, since
# otherwise allow_discussion will always return False
portal_types = getToolByName(self.portal, 'portal_types')
document_fti = getattr(portal_types, 'Document')
document_fti.manage_changeProperties(allow_discussion = True)
# Check if conversation is enabled now
self.assertEquals(conversation.enabled, True)
# Disable commenting in the registry
registry = queryUtility(IRegistry)
settings = registry.for_interface(IDiscussionSettings)
settings.globally_enabled = False
# Check if commenting is disabled on the conversation
self.assertEquals(conversation.enabled, False)
# Enable discussion again
settings.globally_enabled = True
self.assertEquals(conversation.enabled, True)
def test_disable_commenting_for_content_type(self):
# Create a conversation.
conversation = IConversation(self.portal.doc1)
# The Document content type is disabled by default
self.assertEquals(conversation.enabled, False)
# Allow discussion on Document content type
portal_types = getToolByName(self.portal, 'portal_types')
document_fti = getattr(portal_types, 'Document')
document_fti.manage_changeProperties(allow_discussion = True)
# Check if conversation is enabled now
self.assertEquals(conversation.enabled, True)
# Disallow discussion on Document content type
portal_types = getToolByName(self.portal, 'portal_types')
document_fti = getattr(portal_types, 'Document')
document_fti.manage_changeProperties(allow_discussion = False)
# Check if conversation is enabled now
self.assertEquals(conversation.enabled, False)
def test_is_discussion_allowed_for_folder(self):
# Create a folder with two content objects. Change allow_discussion
# and check if the content objects inside the folder are commentable.
pass
def test_is_discussion_allowed_on_content_object(self):
# Allow discussion on a single content object
registry = queryUtility(IRegistry)
settings = registry.for_interface(IDiscussionSettings)
# Create a conversation.
conversation = IConversation(self.portal.doc1)
# Discussion is disallowed by default
self.assertEquals(conversation.enabled, False)
# Allow discussion on content object
self.portal_discussion.overrideDiscussionFor(self.portal.doc1, True)
# Check if discussion is now allowed on the content object
self.assertEquals(conversation.enabled, True)
self.portal_discussion.overrideDiscussionFor(self.portal.doc1, False)
self.assertEquals(conversation.enabled, False)
def test_dict_operations(self):
# test dict operations and acquisition wrapping
@ -459,6 +535,16 @@ class ConversationTest(PloneTestCase):
self.assertEquals(('', 'plone', 'doc1', '++conversation++default'), conversation.getPhysicalPath())
self.assertEquals('plone/doc1/%2B%2Bconversation%2B%2Bdefault', conversation.absolute_url())
def test_parent(self):
# Check that conversation has a content object as parent
# Create a conversation.
conversation = IConversation(self.portal.doc1)
# Check the parent
self.failUnless(conversation.__parent__)
self.assertEquals(conversation.__parent__.getId(), 'doc1')
def test_discussion_item_not_in_bad_types(self):
self.failIf('Discussion Item' in BAD_TYPES)