Turn getComments() and getThreads() into generators.
svn path=/plone.app.discussion/trunk/; revision=27090
This commit is contained in:
parent
2754d897e4
commit
a1e11cb5ee
@ -69,7 +69,7 @@ class Conversation(Traversable, Persistent, Explicit):
|
||||
# id -> comment - find comment by id
|
||||
self._comments = LOBTree()
|
||||
|
||||
# id -> IISet (children) - find all children for a given comment. 0 signifies root.
|
||||
# id -> LLSet (children) - find all children for a given comment. 0 signifies root.
|
||||
self._children = LOBTree()
|
||||
|
||||
def getId(self):
|
||||
@ -101,14 +101,44 @@ class Conversation(Traversable, Persistent, Explicit):
|
||||
def getComments(self, start=0, size=None):
|
||||
"""Get unthreaded comments
|
||||
"""
|
||||
# TODO - batching
|
||||
return self._comments.values()
|
||||
count = 0l
|
||||
for comment in self._comments.values(min=start):
|
||||
yield comment
|
||||
|
||||
count += 1
|
||||
if size and count > size:
|
||||
return
|
||||
|
||||
def getThreads(self, start=0, size=None, root=None, depth=None):
|
||||
def getThreads(self, start=0, size=None, root=0, depth=None):
|
||||
"""Get threaded comments
|
||||
"""
|
||||
# TODO - build threads
|
||||
return []
|
||||
|
||||
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)
|
||||
if children is not None:
|
||||
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
|
||||
|
||||
def addComment(self, comment):
|
||||
"""Add a new comment. The parent id should have been set already. The
|
||||
|
@ -53,31 +53,42 @@ class IConversation(IIterableMapping):
|
||||
"""
|
||||
|
||||
def getComments(start=0, size=None):
|
||||
"""Return a batch of comment objects for rendering. The 'start'
|
||||
parameter is the id of the comment from which to start the batch.
|
||||
"""Return an iterator of comment objects for rendering.
|
||||
|
||||
The 'start' parameter is the id of the comment from which to start the
|
||||
batch. If no such comment exists, the next higher id will be used.
|
||||
This means that you can use max key from a previous batch + 1 safely.
|
||||
|
||||
The 'size' parameter is the number of comments to return in the
|
||||
batch.
|
||||
|
||||
The comments are returned in creation date order, in the exact batche
|
||||
The comments are returned in creation date order, in the exact batch
|
||||
size specified.
|
||||
"""
|
||||
|
||||
def getThreads(start=0, size=None, root=None, depth=None):
|
||||
"""Return a batch of comment objects for rendering. The 'start'
|
||||
parameter is the id of the comment from which to start the batch.
|
||||
The 'size' parameter is the number of comments to return in the
|
||||
batch. 'root', if given, is the id of the comment to which reply
|
||||
threads will be found. If not given, all threads are returned.
|
||||
def getThreads(start=0, size=None, root=0, depth=None):
|
||||
"""Return a batch of comment objects for rendering.
|
||||
|
||||
The 'start' parameter is the id of the comment from which to start
|
||||
the batch. If no such comment exists, the next higher id will be used.
|
||||
This means that you can use max key from a previous batch + 1 safely.
|
||||
This should be a root level comment.
|
||||
|
||||
The 'size' parameter is the number of threads to return in the
|
||||
batch. Full threads are always returned (although you can stop
|
||||
consuming the iterator if you want to abort).
|
||||
|
||||
'root', if given, is the id of the comment to which reply
|
||||
threads will be found. 'root' itself is not included. If not given,
|
||||
all threads are returned.
|
||||
|
||||
If 'depth' is given, it can be used to limit the depth of threads
|
||||
returned. For example, depth=1 will return only direct replies.
|
||||
|
||||
Comments are returned as a recursive list of '(comment, children)',
|
||||
where 'children' is a similar list of (comment, children), or an empty
|
||||
list of a comment has no direct replies.
|
||||
|
||||
The returned number of comments may be bigger than the batch size,
|
||||
in order to give enough context to show the full lineage of the
|
||||
starting comment.
|
||||
Comments are returned as an iterator of dicts with keys 'comment',
|
||||
the comment, 'id', the comment id, and 'depth', which is 0 for
|
||||
top-level comments, 1 for their replies, and so on. The list is
|
||||
returned in depth-first order.
|
||||
"""
|
||||
|
||||
class IReplies(IIterableMapping):
|
||||
|
@ -10,8 +10,6 @@ from plone.app.discussion.tests.layer import DiscussionLayer
|
||||
|
||||
from plone.app.discussion.interfaces import IConversation, IComment, IReplies
|
||||
|
||||
from plone.app.discussion.conversation import ConversationReplies
|
||||
|
||||
class ConversationTest(PloneTestCase):
|
||||
|
||||
layer = DiscussionLayer
|
||||
@ -44,7 +42,7 @@ class ConversationTest(PloneTestCase):
|
||||
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(list(conversation.getComments())), 1)
|
||||
# XXX: not yet implemented
|
||||
# self.assertEquals(len(conversation.getThreads()), 1)
|
||||
self.assertEquals(conversation.total_comments, 1)
|
||||
@ -68,7 +66,7 @@ class ConversationTest(PloneTestCase):
|
||||
new_id = conversation.addComment(comment)
|
||||
|
||||
# make sure the comment has been added
|
||||
self.assertEquals(len(conversation.getComments()), 1)
|
||||
self.assertEquals(len(list(conversation.getComments())), 1)
|
||||
# XXX: not yet implemented
|
||||
# self.assertEquals(len(conversation.getThreads()), 1)
|
||||
self.assertEquals(conversation.total_comments, 1)
|
||||
@ -77,7 +75,7 @@ class ConversationTest(PloneTestCase):
|
||||
del conversation[new_id]
|
||||
|
||||
# make sure there is no comment left in the conversation
|
||||
self.assertEquals(len(conversation.getComments()), 0)
|
||||
self.assertEquals(len(list(conversation.getComments())), 0)
|
||||
|
||||
# XXX: not yet implemented
|
||||
# self.assertEquals(len(conversation.getThreads()), 0)
|
||||
@ -293,16 +291,88 @@ class ConversationTest(PloneTestCase):
|
||||
self.assert_(conversation.last_comment_date < datetime.now() - timedelta(days=3, hours=23, minutes=59, seconds=59))
|
||||
self.assert_(conversation.last_comment_date > datetime.now() - timedelta(days=4, seconds=1))
|
||||
|
||||
def test_get_comments_flat(self):
|
||||
def test_get_comments_full(self):
|
||||
pass
|
||||
|
||||
def test_get_comments_batched(self):
|
||||
pass
|
||||
|
||||
def test_get_threads(self):
|
||||
pass
|
||||
|
||||
# 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)
|
||||
|
||||
replies = IReplies(conversation)
|
||||
|
||||
# Create a nested comment structure:
|
||||
#
|
||||
# Conversation
|
||||
# +- Comment 1
|
||||
# +- Comment 1_1
|
||||
# | +- Comment 1_1_1
|
||||
# +- Comment 1_2
|
||||
# +- Comment 2
|
||||
# +- Comment 2_1
|
||||
|
||||
# Create all comments
|
||||
comment1 = createObject('plone.Comment')
|
||||
comment1.title = 'Comment 1'
|
||||
comment1.text = 'Comment text'
|
||||
|
||||
comment1_1 = createObject('plone.Comment')
|
||||
comment1_1.title = 'Re: Comment 1'
|
||||
comment1_1.text = 'Comment text'
|
||||
|
||||
comment1_1_1 = createObject('plone.Comment')
|
||||
comment1_1_1.title = 'Re: Re: Comment 1'
|
||||
comment1_1_1.text = 'Comment text'
|
||||
|
||||
comment1_2 = createObject('plone.Comment')
|
||||
comment1_2.title = 'Re: Comment 1 (2)'
|
||||
comment1_2.text = 'Comment text'
|
||||
|
||||
comment2 = createObject('plone.Comment')
|
||||
comment2.title = 'Comment 2'
|
||||
comment2.text = 'Comment text'
|
||||
|
||||
comment2_1 = createObject('plone.Comment')
|
||||
comment2_1.title = 'Re: Comment 2'
|
||||
comment2_1.text = 'Comment text'
|
||||
|
||||
# Create the nested comment structure
|
||||
new_id_1 = conversation.addComment(comment1)
|
||||
new_id_2 = conversation.addComment(comment2)
|
||||
|
||||
comment1_1.in_reply_to = new_id_1
|
||||
new_id_1_1 = conversation.addComment(comment1_1)
|
||||
|
||||
comment1_1_1.in_reply_to = new_id_1_1
|
||||
new_id_1_1_1 = conversation.addComment(comment1_1_1)
|
||||
|
||||
comment1_2.in_reply_to = new_id_1
|
||||
new_id_1_2 = conversation.addComment(comment1_2)
|
||||
|
||||
comment2_1.in_reply_to = new_id_2
|
||||
new_id_2_1 = conversation.addComment(comment2_1)
|
||||
|
||||
# Get threads
|
||||
|
||||
self.assertEquals(
|
||||
[{'comment': comment1, 'depth': 0, 'id': new_id_1},
|
||||
{'comment': comment1_1, 'depth': 1, 'id': new_id_1_1},
|
||||
{'comment': comment1_1_1, 'depth': 2, 'id': new_id_1_1_1},
|
||||
{'comment': comment1_2, 'depth': 1, 'id': new_id_1_2},
|
||||
{'comment': comment2, 'depth': 0, 'id': new_id_2},
|
||||
{'comment': comment2_1, 'depth': 1, 'id': new_id_2_1},
|
||||
], list(conversation.getThreads()))
|
||||
|
||||
def test_get_threads_batched(self):
|
||||
# TODO: test start, size, root and depth arguments to getThreads()
|
||||
# - may want to split this into multiple tests
|
||||
pass
|
||||
|
||||
def test_traversal(self):
|
||||
|
Loading…
Reference in New Issue
Block a user