def test_delete_comment(self): # Add and remove a comment to a CommentReplies adapter # 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) # Add a comment to the conversation replies = IReplies(conversation) comment = createObject('plone.Comment') comment.text = 'Comment text' new_id = replies.addComment(comment) comment = self.portal.doc1.restrictedTraverse( '++conversation++default/{0}'.format(new_id), ) # Add a reply to the CommentReplies adapter of the first comment re_comment = createObject('plone.Comment') re_comment.text = 'Comment text' replies = IReplies(comment) new_re_id = replies.addComment(re_comment) # Remove the reply to the CommentReplies adapter del replies[new_re_id] # Make sure there is no comment left in CommentReplies self.assertEqual(len(replies), 0) # Make sure the first comment is still in the conversation self.assertEqual(conversation.total_comments(), 1)
def setUp(self): # Setup sandbox self.portal = self.layer['portal'] self.request = self.layer['request'] setRoles(self.portal, TEST_USER_ID, ['Manager']) self.document = self.portal['doc1'] conversation = IConversation(self.document) replies = IReplies(conversation) comment = createObject('plone.Comment') comment.text = 'This is a comment' new_id = replies.addComment(comment) comment = self.document.restrictedTraverse( '++conversation++default/{0}'.format(new_id) ) re_comment = createObject('plone.Comment') re_comment.text = 'This is a reply' re_comment.author_username = '******' re_comment.author_name = 'Juliana' re_comment.author_email = '*****@*****.**' replies = IReplies(comment) replies.addComment(re_comment)
def test_add_comment(self): # Add comments to a CommentReplies adapter # 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) # Add a comment to the conversation replies = IReplies(conversation) comment = createObject('plone.Comment') comment.text = 'Comment text' new_id = replies.addComment(comment) comment = self.portal.doc1.restrictedTraverse( '++conversation++default/{0}'.format(new_id), ) # Add a reply to the CommentReplies adapter of the first comment re_comment = createObject('plone.Comment') re_comment.text = 'Comment text' replies = IReplies(comment) new_re_id = replies.addComment(re_comment) # check that replies provides the IReplies interface self.assertTrue(IReplies.providedBy(replies)) # Make sure our comment was added self.assertTrue(new_re_id in replies) # Make sure it is also reflected in the conversation self.assertTrue(new_re_id in conversation) # Make sure the conversation has the correct comment id self.assertEqual(conversation[new_re_id].comment_id, new_re_id)
def setUp(self): # Setup sandbox self.portal = self.layer['portal'] self.request = self.layer['request'] setRoles(self.portal, TEST_USER_ID, ['Manager']) name = self.portal.invokeFactory(id='doc1', title='Document 1', type_name='Document') self.document = self.portal[name] conversation = IConversation(self.document) replies = IReplies(conversation) comment = createObject('plone.Comment') comment.text = 'This is a comment' new_id = replies.addComment(comment) comment = self.document.restrictedTraverse( '++conversation++default/%s' % new_id) re_comment = createObject('plone.Comment') re_comment.text = 'This is a reply' re_comment.author_username = "******" re_comment.author_name = "Juliana" re_comment.author_email = "*****@*****.**" replies = IReplies(comment) new_re_id = replies.addComment(re_comment)
def handleComment(self, action): context = aq_inner(self.context) # Check if conversation is enabled on this content object if not self.__parent__.restrictedTraverse( '@@conversation_view').enabled(): raise Unauthorized( 'Discussion is not enabled for this content object.') # Validation form data, errors = self.extractData() if errors: return # Validate Captcha registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) portal_membership = getToolByName(self.context, 'portal_membership') captcha_enabled = settings.captcha != 'disabled' anonymous_comments = settings.anonymous_comments anon = portal_membership.isAnonymousUser() if captcha_enabled and anonymous_comments and anon: if 'captcha' not in data: data['captcha'] = u'' captcha = CaptchaValidator(self.context, self.request, None, ICaptcha['captcha'], None) captcha.validate(data['captcha']) # Create comment comment = self.create_comment(data) # Add comment to conversation conversation = IConversation(self.__parent__) if data['in_reply_to']: # Add a reply to an existing comment conversation_to_reply_to = conversation.get(data['in_reply_to']) replies = IReplies(conversation_to_reply_to) comment_id = replies.addComment(comment) else: # Add a comment to the conversation comment_id = conversation.addComment(comment) # Redirect after form submit: # If a user posts a comment and moderation is enabled, a message is # shown to the user that his/her comment awaits moderation. If the user # has 'review comments' permission, he/she is redirected directly # to the comment. can_review = getSecurityManager().checkPermission( 'Review comments', context) workflowTool = getToolByName(context, 'portal_workflow') comment_review_state = workflowTool.getInfoFor(comment, 'review_state', None) if comment_review_state == 'pending' and not can_review: # Show info message when comment moderation is enabled IStatusMessage(self.context.REQUEST).addStatusMessage( _('Your comment awaits moderator approval.'), type='info') self.request.response.redirect(self.action) else: # Redirect to comment (inside a content object page) self.request.response.redirect(self.action + '#' + str(comment_id))
def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] self.request = self.layer["request"] self.portal_url = self.portal.absolute_url() # Allow discussion registry = getUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) settings.globally_enabled = True settings.edit_comment_enabled = True settings.delete_own_comment_enabled = True # doc with comments self.doc = api.content.create( container=self.portal, type="Document", id="doc_with_comments", title="Document with comments", allow_discussion=True, ) self.conversation = IConversation(self.doc) self.replies = IReplies(self.conversation) comment = createObject("plone.Comment") comment.text = "Comment" self.comment = self.replies[self.replies.addComment(comment)] comment = createObject("plone.Comment") comment.text = "Comment 2" self.replies.addComment(comment)
def test_delete_recursive(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) 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.text = 'Comment text' comment1_1 = createObject('plone.Comment') comment1_1.text = 'Comment text' comment1_1_1 = createObject('plone.Comment') comment1_1_1.text = 'Comment text' comment1_2 = createObject('plone.Comment') comment1_2.text = 'Comment text' comment2 = createObject('plone.Comment') comment2.text = 'Comment text' comment2_1 = createObject('plone.Comment') 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 conversation.addComment(comment1_1_1) comment1_2.in_reply_to = new_id_1 conversation.addComment(comment1_2) comment2_1.in_reply_to = new_id_2 new_id_2_1 = conversation.addComment(comment2_1) del conversation[new_id_1] self.assertEqual( [{'comment': comment2, 'depth': 0, 'id': new_id_2}, {'comment': comment2_1, 'depth': 1, 'id': new_id_2_1}, ], list(conversation.getThreads()))
def test_removedEvent(self): self.assertFalse(self.registry.replyRemoved) conversation = IConversation(self.portal.doc1) replies = IReplies(conversation) comment = createObject('plone.Comment') comment.text = 'Comment text' new_id = replies.addComment(comment) comment = self.portal.doc1.restrictedTraverse( '++conversation++default/%s' % new_id) re_comment = createObject('plone.Comment') re_comment.text = 'Comment text' replies = IReplies(comment) new_re_id = replies.addComment(re_comment) del replies[new_re_id] self.assertTrue(self.registry.replyRemoved)
def create_comments(self, document): document.allow_discussion = True conversation = IConversation(document) replies = IReplies(conversation) comments = [] for x in range(1, 2): comment = createObject("plone.Comment") comment.text = "Comment %d" % x comment = replies[replies.addComment(comment)] comment_replies = IReplies(comment) for y in range(1, 2): comment = createObject("plone.Comment") comment.text = "Comment %d.%d" % (x, y) comment_replies.addComment(comment) comments.append(comment) return comments
def test_addEvent(self): self.assertFalse(self.registry.replyAdded) conversation = IConversation(self.document) replies = IReplies(conversation) comment = createObject('plone.Comment') comment.text = 'Comment text' new_id = replies.addComment(comment) comment = self.document.restrictedTraverse( '++conversation++default/%s' % new_id) re_comment = createObject('plone.Comment') re_comment.text = 'Comment text' replies = IReplies(comment) replies.addComment(re_comment) self.assertTrue(self.registry.replyAdded)
def test_comment_with_no_author_image(self): setRoles(self.portal, TEST_USER_ID, ["Manager"]) self.conversation = IConversation(self.doc) self.replies = IReplies(self.conversation) comment = createObject("plone.Comment") comment.text = "Hey ho, let's go!" comment.author_username = TEST_USER_ID self.comment = self.replies[self.replies.addComment(comment)] serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson) self.assertEqual( None, serializer().get("author_image"), )
def test_comment_with_mimetype_text_plain(self): self.conversation = IConversation(self.doc) self.replies = IReplies(self.conversation) comment = createObject("plone.Comment") comment.text = "Hey, I am plain text!" comment.mime_type = "text/plain" self.comment = self.replies[self.replies.addComment(comment)] serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson) # serializer should return HTML with a clickable link self.assertEqual( "Hey, I am plain text!", serializer()["text"]["data"], ) # serializer should return mimetype = text/x-web-intelligent self.assertEqual("text/plain", serializer()["text"]["mime-type"])
def test_comment_with_author_image(self): setRoles(self.portal, TEST_USER_ID, ["Manager"]) # set member portrait membertool = getToolByName(self.portal, "portal_memberdata") membertool._setPortrait( Image(id=TEST_USER_ID, file=dummy.File(), title=""), TEST_USER_ID) self.conversation = IConversation(self.doc) self.replies = IReplies(self.conversation) comment = createObject("plone.Comment") comment.text = "Hey ho, let's go!" comment.author_username = TEST_USER_ID self.comment = self.replies[self.replies.addComment(comment)] serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson) self.assertEqual( f"{self.portal_url}/portal_memberdata/portraits/test_user_1_", serializer().get("author_image"), )
def test_comment_with_mimetype_html(self): # Set text transform to text/html registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) settings.text_transform = "text/html" self.conversation = IConversation(self.doc) self.replies = IReplies(self.conversation) comment = createObject("plone.Comment") comment.text = "Go to <a href='https://www.plone.org'>Plone</a>" comment.mime_type = "text/html" self.comment = self.replies[self.replies.addComment(comment)] serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson) # serializer should return HTML self.assertEqual( 'Go to <a href="https://www.plone.org">Plone</a>', serializer()["text"]["data"], ) # serializer should return mimetype = text/html self.assertEqual("text/html", serializer()["text"]["mime-type"])
def test_delete_comment(self): # Create and remove a comment and check if the replies adapter # has been updated accordingly # 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) replies = IReplies(conversation) # Add a comment. comment = createObject('plone.Comment') comment.text = 'Comment text' new_id = replies.addComment(comment) # make sure the comment has been added self.assertEqual(len(replies), 1) # delete the comment we just created del replies[new_id] # make sure there is no comment left in the conversation self.assertEqual(len(replies), 0)
def test_add_comment(self): # Add comments to a ConversationReplies adapter # 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) replies = IReplies(conversation) comment = createObject('plone.Comment') comment.text = 'Comment text' new_id = replies.addComment(comment) # check that replies provides the IReplies interface self.assertTrue(IReplies.providedBy(replies)) # Make sure our comment was added self.assertTrue(new_id in replies) # Make sure it is also reflected in the conversation self.assertTrue(new_id in conversation) self.assertEqual(conversation[new_id].comment_id, new_id)
def test_comment_with_mimetype_intelligent_text(self): # Set text transform to intelligent text registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) settings.text_transform = "text/x-web-intelligent" self.conversation = IConversation(self.doc) self.replies = IReplies(self.conversation) comment = createObject("plone.Comment") comment.text = "Go to https://www.plone.org" comment.mime_type = "text/x-web-intelligent" self.comment = self.replies[self.replies.addComment(comment)] serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson) # serializer should return HTML with a clickable link self.assertEqual( 'Go to <a href="https://www.plone.org" ' + 'rel="nofollow">https://www.plone.org</a>', normalize_html(serializer()["text"]["data"]), ) # serializer should return mimetype = text/html self.assertEqual("text/html", serializer()["text"]["mime-type"])
def migrate_replies(context, in_reply_to, replies, depth=0, just_delete=0): # Recursive function to migrate all direct replies # of a comment. Returns True if there are no replies to # this comment left, and therefore the comment can be removed. if len(replies) == 0: return True for reply in replies: # log indent = " " for i in range(depth): indent += " " log("%smigrate_reply: '%s'." % (indent, reply.title)) should_migrate = True if filter_callback and not filter_callback(reply): should_migrate = False if just_delete: should_migrate = False new_in_reply_to = None if should_migrate: # create a reply object comment = CommentFactory() comment.title = reply.Title() comment.text = reply.cooked_text comment.mime_type = 'text/html' comment.creator = reply.Creator() email = reply.getProperty('email', None) if email: comment.author_email = email comment.creation_date = DT2dt(reply.creation_date) comment.modification_date = DT2dt(reply.modification_date) comment.reply_to = in_reply_to if in_reply_to == 0: # Direct reply to a content object new_in_reply_to = conversation.addComment(comment) else: # Reply to another comment comment_to_reply_to = conversation.get(in_reply_to) replies = IReplies(comment_to_reply_to) try: comment.text.encode("utf-8") new_in_reply_to = replies.addComment(comment) except UnicodeDecodeError, e: log("Fixing UnicodeDecodeError %s" % e) comment.text = comment.text.decode("utf-8") new_in_reply_to = replies.addComment(comment) total_comments_migrated[0] += 1 # migrate all talkbacks of the reply talkback = getattr( reply, 'talkback', None) no_replies_left = migrate_replies(context, new_in_reply_to, talkback.getReplies(), depth=depth+1, just_delete=not should_migrate) if no_replies_left: # remove reply and talkback talkback.deleteReply(reply.id) obj = aq_parent(talkback) obj.talkback = None log("%sremove %s" % (indent, reply.id)) total_comments_deleted[0] += 1
def test_dict_api(self): # This test is for the ConversationReplies as well as the # CommentReplies adapter. # # Ensure all operations use only top-level comments. Add some # deeper children and ensure that these are not exposed through the # IReplies dict. # 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) 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.text = 'Comment text' comment1_1 = createObject('plone.Comment') comment1_1.text = 'Comment text' comment1_1_1 = createObject('plone.Comment') comment1_1_1.text = 'Comment text' comment1_2 = createObject('plone.Comment') comment1_2.text = 'Comment text' comment2 = createObject('plone.Comment') comment2.text = 'Comment text' comment2_1 = createObject('plone.Comment') comment2_1.text = 'Comment text' # Create the nested comment structure new_id_1 = replies.addComment(comment1) comment1 = self.portal.doc1.restrictedTraverse( '++conversation++default/%s' % new_id_1) replies_to_comment1 = IReplies(comment1) new_id_2 = replies.addComment(comment2) comment2 = self.portal.doc1.restrictedTraverse( '++conversation++default/%s' % new_id_2) replies_to_comment2 = IReplies(comment2) new_id_1_1 = replies_to_comment1.addComment(comment1_1) comment1_1 = self.portal.doc1.restrictedTraverse( '++conversation++default/%s' % new_id_1_1) replies_to_comment1_1 = IReplies(comment1_1) replies_to_comment1_1.addComment(comment1_1_1) replies_to_comment1.addComment(comment1_2) replies_to_comment2.addComment(comment2_1) # check that replies only contain the direct comments # and no comments deeper than 1 self.assertEqual(conversation.total_comments, 6) self.assertEqual(len(replies), 2) self.assertEqual(len(replies_to_comment1), 2) self.assertEqual(len(replies_to_comment1_1), 1) self.assertEqual(len(replies_to_comment2), 1)
def migrate_replies(context, in_reply_to, replies, depth=0, just_delete=0): # Recursive function to migrate all direct replies # of a comment. Returns True if there are no replies to # this comment left, and therefore the comment can be removed. if len(replies) == 0: return True workflow = context.portal_workflow oldchain = workflow.getChainForPortalType('Discussion Item') new_workflow = workflow.comment_review_workflow if type(oldchain) == TupleType and len(oldchain) > 0: oldchain = oldchain[0] for reply in replies: # log indent = " " for i in range(depth): indent += " " log("%smigrate_reply: '%s'." % (indent, reply.title)) should_migrate = True if filter_callback and not filter_callback(reply): should_migrate = False if just_delete: should_migrate = False new_in_reply_to = None if should_migrate: # create a reply object comment = CommentFactory() comment.title = reply.Title() comment.text = reply.cooked_text comment.mime_type = 'text/html' comment.creator = reply.Creator() email = reply.getProperty('email', None) if email: comment.author_email = email comment.creation_date = DT2dt(reply.creation_date) comment.modification_date = DT2dt(reply.modification_date) comment.reply_to = in_reply_to if in_reply_to == 0: # Direct reply to a content object new_in_reply_to = conversation.addComment(comment) else: # Reply to another comment comment_to_reply_to = conversation.get(in_reply_to) replies = IReplies(comment_to_reply_to) new_in_reply_to = replies.addComment(comment) # migrate the review state old_status = workflow.getStatusOf(oldchain, reply) new_status = { 'action': None, 'actor': None, 'comment': 'Migrated workflow state', 'review_state': old_status and old_status.get( 'review_state', new_workflow.initial_state) or 'published', 'time': DateTime() } workflow.setStatusOf('comment_review_workflow', comment, new_status) auto_transition = new_workflow._findAutomaticTransition( comment, new_workflow._getWorkflowStateOf(comment)) if auto_transition is not None: new_workflow._changeStateOf(comment, auto_transition) else: new_workflow.updateRoleMappingsFor(comment) comment.reindexObject( idxs=['allowedRolesAndUsers', 'review_state']) self.total_comments_migrated += 1 # migrate all talkbacks of the reply talkback = getattr(reply, 'talkback', None) no_replies_left = migrate_replies( context, new_in_reply_to, talkback.getReplies(), depth=depth + 1, just_delete=not should_migrate) if no_replies_left: # remove reply and talkback talkback.deleteReply(reply.id) obj = aq_parent(talkback) obj.talkback = None log("%sremove %s" % (indent, reply.id)) self.total_comments_deleted += 1 # Return True when all comments on a certain level have been # migrated. return True
def handleComment(self, action): context = aq_inner(self.context) # Check if conversation is enabled on this content object if not self.__parent__.restrictedTraverse( '@@conversation_view').enabled(): raise Unauthorized("Discussion is not enabled for this content " "object.") # Validation form data, errors = self.extractData() if errors: return # Validate Captcha registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings, check=False) portal_membership = getToolByName(self.context, 'portal_membership') if settings.captcha != 'disabled' and \ settings.anonymous_comments and \ portal_membership.isAnonymousUser(): if not 'captcha' in data: data['captcha'] = u"" captcha = CaptchaValidator(self.context, self.request, None, ICaptcha['captcha'], None) captcha.validate(data['captcha']) # some attributes are not always set author_name = u"" # Create comment comment = createObject('plone.Comment') # Set comment mime type to current setting in the discussion registry comment.mime_type = settings.text_transform # Set comment attributes (including extended comment form attributes) for attribute in self.fields.keys(): setattr(comment, attribute, data[attribute]) # Make sure author_name is properly encoded if 'author_name' in data: author_name = data['author_name'] if isinstance(author_name, str): author_name = unicode(author_name, 'utf-8') # Set comment author properties for anonymous users or members can_reply = getSecurityManager().checkPermission( 'Reply to item', context) portal_membership = getToolByName(self.context, 'portal_membership') if portal_membership.isAnonymousUser() and \ settings.anonymous_comments: # Anonymous Users comment.author_name = author_name comment.author_email = u"" comment.user_notification = None comment.creation_date = datetime.utcnow() comment.modification_date = datetime.utcnow() elif not portal_membership.isAnonymousUser() and can_reply: # Member member = portal_membership.getAuthenticatedMember() username = member.getUserName() email = member.getProperty('email') fullname = member.getProperty('fullname') if not fullname or fullname == '': fullname = member.getUserName() # memberdata is stored as utf-8 encoded strings elif isinstance(fullname, str): fullname = unicode(fullname, 'utf-8') if email and isinstance(email, str): email = unicode(email, 'utf-8') comment.creator = username comment.author_username = username comment.author_name = fullname comment.author_email = email comment.creation_date = datetime.utcnow() comment.modification_date = datetime.utcnow() else: # pragma: no cover raise Unauthorized( "Anonymous user tries to post a comment, but " "anonymous commenting is disabled. Or user does not have the " "'reply to item' permission.") # Add comment to conversation conversation = IConversation(self.__parent__) if data['in_reply_to']: # Add a reply to an existing comment conversation_to_reply_to = conversation.get(data['in_reply_to']) replies = IReplies(conversation_to_reply_to) comment_id = replies.addComment(comment) else: # Add a comment to the conversation comment_id = conversation.addComment(comment) # Redirect after form submit: # If a user posts a comment and moderation is enabled, a message is # shown to the user that his/her comment awaits moderation. If the user # has 'review comments' permission, he/she is redirected directly # to the comment. can_review = getSecurityManager().checkPermission( 'Review comments', context) workflowTool = getToolByName(context, 'portal_workflow') comment_review_state = workflowTool.getInfoFor(comment, 'review_state') if comment_review_state == 'pending' and not can_review: # Show info message when comment moderation is enabled IStatusMessage(self.context.REQUEST).addStatusMessage( _("Your comment awaits moderator approval."), type="info") self.request.response.redirect(self.action) else: # Redirect to comment (inside a content object page) self.request.response.redirect(self.action + '#' + str(comment_id))
def can_delete(self, comment=None): comment = comment or self.context return (len(IReplies(aq_inner(comment))) == 0 and self.could_delete(comment=comment))
def migrate_replies(context, in_reply_to, replies, depth=0, just_delete=0): """migrate_replies""" # Recursive function to migrate all direct replies # of a comment. Returns True if there are no replies to # this comment left, and therefore the comment can be removed. if not replies: return True for reply in replies: # log indent = " " * depth + 1 log("%smigrate_reply: '%s'." % (indent, reply.title)) should_migrate = True if filter_callback and not filter_callback(reply): should_migrate = False if just_delete: should_migrate = False new_in_reply_to = None if should_migrate: # create a reply object comment = CommentFactory() comment.title = reply.Title() comment.text = reply.cooked_text comment.mime_type = 'text/html' comment.creator = reply.Creator() email = reply.getProperty('email', None) if email: comment.author_email = email comment.creation_date = DT2dt(reply.creation_date) comment.modification_date = DT2dt(reply.modification_date) comment.in_reply_to = in_reply_to if in_reply_to == 0: # Direct reply to a content object new_in_reply_to = conversation.addComment(comment) else: # Reply to another comment comment_to_reply_to = conversation.get(in_reply_to) replies = IReplies(comment_to_reply_to) new_in_reply_to = replies.addComment(comment) self.total_comments_migrated += 1 # migrate all talkbacks of the reply talkback = getattr(reply, 'talkback', None) no_replies_left = migrate_replies(context, new_in_reply_to, talkback.getReplies(), depth=depth+1, just_delete=not should_migrate) if no_replies_left: # remove reply and talkback talkback.deleteReply(reply.id) obj = aq_parent(talkback) obj.talkback = None log("%sremove %s" % (indent, reply.id)) self.total_comments_deleted += 1 # Return True when all comments on a certain level have been # migrated. return True
def test_traversal(self): # Create a nested structure of comment replies and check the traversal # make sure comments are traversable, have an id, absolute_url and # physical path conversation = IConversation(self.portal.doc1) comment1 = createObject('plone.Comment') comment1.text = 'Comment text' conversation.addComment(comment1) comment = createObject('plone.Comment') comment.text = 'Comment text' new_id = conversation.addComment(comment) comment = self.portal.doc1.restrictedTraverse( '++conversation++default/{0}'.format(new_id), ) # Add a reply to the CommentReplies adapter of the first comment re_comment = createObject('plone.Comment') re_comment.text = 'Comment text' replies = IReplies(comment) new_re_id = replies.addComment(re_comment) re_comment = self.portal.doc1.restrictedTraverse( '++conversation++default/{0}'.format(new_re_id), ) # Add a reply to the reply re_re_comment = createObject('plone.Comment') re_re_comment.text = 'Comment text' replies = IReplies(re_comment) new_re_re_id = replies.addComment(re_re_comment) re_re_comment = self.portal.doc1.restrictedTraverse( '++conversation++default/{0}'.format(new_re_re_id), ) # Add a reply to the replies reply re_re_re_comment = createObject('plone.Comment') re_re_re_comment.text = 'Comment text' replies = IReplies(re_re_comment) new_re_re_re_id = replies.addComment(re_re_re_comment) re_re_re_comment = self.portal.doc1.restrictedTraverse( '++conversation++default/{0}'.format(new_re_re_re_id), ) self.assertEqual( ('', 'plone', 'doc1', '++conversation++default', str(new_id)), comment.getPhysicalPath(), ) self.assertEqual( 'http://nohost/plone/doc1/++conversation++default/' + str(new_id), comment.absolute_url(), ) self.assertEqual( ('', 'plone', 'doc1', '++conversation++default', str(new_re_id)), re_comment.getPhysicalPath(), ) self.assertEqual( 'http://nohost/plone/doc1/++conversation++default/' + str(new_re_id), re_comment.absolute_url(), ) self.assertEqual( ( '', 'plone', 'doc1', '++conversation++default', str(new_re_re_id), ), re_re_comment.getPhysicalPath(), ) self.assertEqual( 'http://nohost/plone/doc1/++conversation++default/' + str(new_re_re_id), re_re_comment.absolute_url(), ) self.assertEqual( ( '', 'plone', 'doc1', '++conversation++default', str(new_re_re_re_id), ), re_re_re_comment.getPhysicalPath(), ) self.assertEqual( 'http://nohost/plone/doc1/++conversation++default/' + str(new_re_re_re_id), re_re_re_comment.absolute_url(), )