diff --git a/plone/app/discussion/comment.py b/plone/app/discussion/comment.py index d7c3dc2..bd7d08b 100644 --- a/plone/app/discussion/comment.py +++ b/plone/app/discussion/comment.py @@ -42,15 +42,13 @@ class Comment(Explicit, Traversable, RoleManager, Owned): author_name = None author_email = None - def __init__(self, conversation=None, **kw): - self.comment_id = None # will be set by IConversation.addComment() - - self.__parent__ = conversation + # Note: we want to use zope.component.createObject() to instantiate + # comments as far as possible. comment_id and __parent__ are set via + # IConversation.addComment(). + + def __init__(self): self.creation_date = self.modification_date = datetime.now() - for k, v in kw.items(): - setattr(self, k, v) - @property def __name__(self): return self.comment_id and unicode(self.comment_id) or None diff --git a/plone/app/discussion/configure.zcml b/plone/app/discussion/configure.zcml index 6f3dd5f..bd8d91b 100644 --- a/plone/app/discussion/configure.zcml +++ b/plone/app/discussion/configure.zcml @@ -22,9 +22,10 @@ - + diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py index b4dd8a8..5ce861d 100644 --- a/plone/app/discussion/conversation.py +++ b/plone/app/discussion/conversation.py @@ -126,7 +126,9 @@ class Conversation(Traversable, Persistent, Explicit): notify(ObjectWillBeAddedEvent(comment, self, id)) self._comments[id] = comment - # for logged in users only + comment.__parent__ = self + + # Record unique users who've commented (for logged in users only) commentator = comment.author_username if commentator: if not commentator in self._commentators: @@ -147,6 +149,8 @@ class Conversation(Traversable, Persistent, Explicit): notify(ObjectAddedEvent(comment.__of__(self), self, id)) notify(ContainerModifiedEvent(self)) + return id + # Dict API def __len__(self): @@ -155,12 +159,10 @@ class Conversation(Traversable, Persistent, Explicit): def __contains__(self, key): return long(key) in self._comments - # TODO: Should __getitem__, get, __iter__, values(), items() and iter* return aq-wrapped comments? - def __getitem__(self, key): """Get an item by its long key """ - return self._comments[long(key)] + return self._comments[long(key)].__of__(self) def __delitem__(self, key): """Delete an item by its long key @@ -168,7 +170,7 @@ class Conversation(Traversable, Persistent, Explicit): key = long(key) - comment = self[key] + comment = self[key].__of__(self) commentator = comment.author_username notify(ObjectWillBeRemovedEvent(comment, self, key)) @@ -187,25 +189,30 @@ class Conversation(Traversable, Persistent, Explicit): return iter(self._comments) def get(self, key, default=None): - return self._comments.get(long(key), default) + comment = self._comments.get(long(key), default) + if comment is default: + return default + return comment.__of__(self) def keys(self): return self._comments.keys() def items(self): - return self._comments.items() + return [(i[0], i[1].__of__(self),) for i in self._comments.items()] def values(self): - return self._comments.values() + return [v.__of__(self) for v in self._comments.values()] def iterkeys(self): return self._comments.iterkeys() def itervalues(self): - return self._comments.itervalues() + for v in self._comments.itervalues(): + yield v.__of__(self) def iteritems(self): - return self._comments.iteritems() + for k, v in self._comments.iteritems(): + yield (k, v.__of__(self),) @implementer(IConversation) @adapter(IAnnotatable) diff --git a/plone/app/discussion/tests/test_api.py b/plone/app/discussion/tests/test_api.py deleted file mode 100644 index 984af14..0000000 --- a/plone/app/discussion/tests/test_api.py +++ /dev/null @@ -1,45 +0,0 @@ -import unittest -from datetime import datetime, timedelta - -from Products.PloneTestCase.ptc import PloneTestCase -from plone.app.discussion.tests.layer import DiscussionLayer - -from plone.app.discussion.conversation import Conversation -from plone.app.discussion.comment import Comment -from plone.app.discussion.interfaces import ICommentingTool, IConversation - -class ConversationTest(PloneTestCase): - - layer = DiscussionLayer - - def afterSetUp(self): - # XXX If we make this a layer, it only get run once... - # First we need to create some content. - self.loginAsPortalOwner() - typetool = self.portal.portal_types - typetool.constructContent('Document', self.portal, 'doc1') - - def test_add_comment(self): - # Create a conversation. In this case we doesn't assign it to an - # object, as we just want to check the Conversation object API. - conversation = IConversation(self.portal.doc1) - # Pretend that we have traversed to the comment by aq wrapping it. - # XXX implement traversal to commenting and change this: - conversation = conversation.__of__(self.portal.doc1) - - # Add a comment. reply_to=0 means it's not a reply - comment = Comment(conversation=conversation, reply_to=0) - comment.title = 'Comment 1' - comment.text = 'Comment text' - - conversation.addComment(comment) - - # Check that the conversation methods return the correct data - self.assert_(isinstance(comment.comment_id, long)) - self.assertEquals(len(conversation.getComments()), 1) - self.assertEquals(len(conversation.getThreads()), 1) - self.assertEquals(conversation.total_comments, 1) - self.assert_(conversation.last_comment_date - datetime.now() < timedelta(seconds=1)) - -def test_suite(): - return unittest.defaultTestLoader.loadTestsFromName(__name__) \ No newline at end of file diff --git a/plone/app/discussion/tests/test_comment.py b/plone/app/discussion/tests/test_comment.py new file mode 100644 index 0000000..aed3445 --- /dev/null +++ b/plone/app/discussion/tests/test_comment.py @@ -0,0 +1,71 @@ +import unittest + +from zope.component import createObject + +from Products.PloneTestCase.ptc import PloneTestCase +from plone.app.discussion.tests.layer import DiscussionLayer + +from plone.app.discussion.interfaces import IComment, IConversation +from plone.app.discussion.comment import Comment + +class CommentTest(PloneTestCase): + + layer = DiscussionLayer + + def afterSetUp(self): + # First we need to create some content. + self.loginAsPortalOwner() + typetool = self.portal.portal_types + typetool.constructContent('Document', self.portal, 'doc1') + + def test_factory(self): + # test with createObject() + pass + + def test_id(self): + # relationship between id, getId(), __name__ + pass + + def test_title(self): + pass + + def test_creator(self): + pass + + def test_traversal(self): + # make sure comments are traversable, have an id, absolute_url and physical path + pass + + def test_workflow(self): + # ensure that we can assign a workflow to the comment type and perform + # workflow operations + pass + + def test_fti(self): + # test that we can look up an FTI for Discussion Item + pass + +class RepliesTest(PloneTestCase): + + # test the IReplies adapter on a comment + + layer = DiscussionLayer + + def afterSetUp(self): + # First we need to create some content. + self.loginAsPortalOwner() + typetool = self.portal.portal_types + typetool.constructContent('Document', self.portal, 'doc1') + + def test_add_comment(self): + pass + + def test_delete_comment(self): + pass + + def test_dict_api(self): + # ensure all operations use only top-level comments + pass + +def test_suite(): + return unittest.defaultTestLoader.loadTestsFromName(__name__) \ No newline at end of file diff --git a/plone/app/discussion/tests/test_conversation.py b/plone/app/discussion/tests/test_conversation.py new file mode 100644 index 0000000..5be7e53 --- /dev/null +++ b/plone/app/discussion/tests/test_conversation.py @@ -0,0 +1,103 @@ +import unittest +from datetime import datetime, timedelta + +from zope.component import createObject + +from Acquisition import aq_base + +from Products.PloneTestCase.ptc import PloneTestCase +from plone.app.discussion.tests.layer import DiscussionLayer + +from plone.app.discussion.interfaces import IConversation, IComment + +class ConversationTest(PloneTestCase): + + layer = DiscussionLayer + + def afterSetUp(self): + # First we need to create some content. + self.loginAsPortalOwner() + typetool = self.portal.portal_types + typetool.constructContent('Document', self.portal, 'doc1') + + def test_add_comment(self): + # Create a conversation. In this case we doesn't assign it to an + # object, as we just want to check the Conversation object API. + conversation = IConversation(self.portal.doc1) + + # Pretend that we have traversed to the comment by aq wrapping it. + conversation = conversation.__of__(self.portal.doc1) + + # Add a comment. Note: in real life, we always create comments via the factory + # to allow different factories to be swapped in + + comment = createObject('plone.Comment') + comment.title = 'Comment 1' + comment.text = 'Comment text' + + new_id = conversation.addComment(comment) + + # Check that the conversation methods return the correct data + self.assert_(isinstance(comment.comment_id, long)) + self.assert_(IComment.providedBy(conversation[new_id])) + self.assertEquals(aq_base(conversation[new_id].__parent__), aq_base(conversation)) + self.assertEquals(new_id, comment.comment_id) + self.assertEquals(len(conversation.getComments()), 1) + self.assertEquals(len(conversation.getThreads()), 1) + self.assertEquals(conversation.total_comments, 1) + self.assert_(conversation.last_comment_date - datetime.now() < timedelta(seconds=1)) + + def test_delete(self): + pass + + def test_dict_operations(self): + # test dict operations and acquisition wrapping + pass + + def test_total_comments(self): + pass + + def test_commentators(self): + # add and remove a few comments to make sure the commenetators + # property returns a true set + pass + + def test_last_comment_date(self): + pass + + def test_get_comments_flat(self): + pass + + def test_get_comments_batched(self): + pass + + def test_get_threads(self): + pass + + def test_get_threads_batched(self): + pass + +class RepliesTest(PloneTestCase): + + # test the IReplies adapter on a conversation + + layer = DiscussionLayer + + def afterSetUp(self): + # First we need to create some content. + self.loginAsPortalOwner() + typetool = self.portal.portal_types + typetool.constructContent('Document', self.portal, 'doc1') + + def test_add_comment(self): + pass + + def test_delete_comment(self): + pass + + def test_dict_api(self): + # ensure all operations use only top-level comments + pass + +def test_suite(): + return unittest.defaultTestLoader.loadTestsFromName(__name__) \ No newline at end of file diff --git a/plone/app/discussion/tests/test_indexers.py b/plone/app/discussion/tests/test_indexers.py new file mode 100644 index 0000000..d2b9235 --- /dev/null +++ b/plone/app/discussion/tests/test_indexers.py @@ -0,0 +1,42 @@ +import unittest + +from Products.PloneTestCase.ptc import PloneTestCase +from plone.app.discussion.tests.layer import DiscussionLayer + +class TestIndexers(PloneTestCase): + + layer = DiscussionLayer + + def test_title(self): + pass + + def test_description(self): + pass + + def test_dates(self): + # created, modified, effective etc + pass + + def test_searchable_text(self): + pass + + def test_creator(self): + pass + + def test_title(self): + pass + + def test_in_reply_to(self): + pass + + def test_path(self): + pass + + def test_review_state(self): + pass + + def test_object_provides(self): + pass + +def test_suite(): + return unittest.defaultTestLoader.loadTestsFromName(__name__) \ No newline at end of file diff --git a/plone/app/discussion/tests/test_tool.py b/plone/app/discussion/tests/test_tool.py index 619a6e8..dab477a 100644 --- a/plone/app/discussion/tests/test_tool.py +++ b/plone/app/discussion/tests/test_tool.py @@ -1,11 +1,10 @@ import unittest -from zope.component import getUtility +from zope.component import getUtility, createObject from Products.PloneTestCase.ptc import PloneTestCase from plone.app.discussion.tests.layer import DiscussionLayer -from plone.app.discussion.comment import Comment from plone.app.discussion.interfaces import ICommentingTool, IConversation class ToolTest(PloneTestCase): @@ -27,8 +26,8 @@ class ToolTest(PloneTestCase): # XXX implement traversal to commenting and change this: conversation = conversation.__of__(self.portal.doc1) - # Add a comment. reply_to=0 means it's not a reply - comment = Comment(conversation=conversation, reply_to=0) + # Add a comment. + comment = createObject('plone.Comment') comment.title = 'Comment 1' comment.text = 'Comment text' @@ -41,5 +40,12 @@ class ToolTest(PloneTestCase): " %s results in the search" % len(comment)) self.assertEquals(comment[0].Title, 'Comment 1') + def test_unindexing(self): + pass + + def test_search(self): + # search returns only comments + pass + def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) \ No newline at end of file