2016-02-05 01:39:53 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
2015-05-03 08:16:39 +02:00
|
|
|
from Acquisition import aq_inner
|
|
|
|
from Acquisition import aq_parent
|
2016-02-05 01:39:53 +01:00
|
|
|
from datetime import datetime
|
2015-05-03 08:16:39 +02:00
|
|
|
from DateTime import DateTime
|
2016-02-05 01:39:53 +01:00
|
|
|
from plone.app.discussion.comment import CommentFactory
|
|
|
|
from plone.app.discussion.interfaces import IComment
|
|
|
|
from plone.app.discussion.interfaces import IConversation
|
|
|
|
from plone.app.discussion.interfaces import IReplies
|
2009-12-22 00:36:35 +01:00
|
|
|
from Products.CMFCore.interfaces._content import IDiscussionResponse
|
2015-05-03 08:16:39 +02:00
|
|
|
from Products.CMFCore.utils import getToolByName
|
|
|
|
from Products.Five.browser import BrowserView
|
2012-06-13 13:17:22 +02:00
|
|
|
from types import TupleType
|
2015-05-03 08:16:39 +02:00
|
|
|
|
|
|
|
import transaction
|
2012-06-13 13:17:22 +02:00
|
|
|
|
2009-07-07 18:16:58 +02:00
|
|
|
|
2011-04-02 23:26:36 +02:00
|
|
|
def DT2dt(DT):
|
|
|
|
"""Convert a Zope DateTime (with timezone) into a Python datetime (GMT)."""
|
|
|
|
DT = DT.toZone('GMT')
|
2012-01-13 14:40:26 +01:00
|
|
|
return datetime(
|
|
|
|
DT.year(),
|
|
|
|
DT.month(),
|
|
|
|
DT.day(),
|
|
|
|
DT.hour(),
|
|
|
|
DT.minute(),
|
|
|
|
int(DT.second()))
|
2011-04-02 23:26:36 +02:00
|
|
|
|
|
|
|
|
2009-07-07 18:16:58 +02:00
|
|
|
class View(BrowserView):
|
|
|
|
"""Migration View
|
|
|
|
"""
|
|
|
|
|
2011-04-02 21:33:10 +02:00
|
|
|
def __call__(self, filter_callback=None):
|
2009-07-07 18:16:58 +02:00
|
|
|
|
|
|
|
context = aq_inner(self.context)
|
|
|
|
out = []
|
2009-07-12 18:09:34 +02:00
|
|
|
self.total_comments_migrated = 0
|
2009-07-13 22:22:37 +02:00
|
|
|
self.total_comments_deleted = 0
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2016-02-05 01:39:53 +01:00
|
|
|
dry_run = 'dry_run' in self.request
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2010-01-03 18:36:01 +01:00
|
|
|
# This is for testing only.
|
|
|
|
# Do not use transactions during a test.
|
2016-02-05 01:39:53 +01:00
|
|
|
test = 'test' in self.request
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2010-01-03 18:36:01 +01:00
|
|
|
if not test:
|
2012-01-13 14:40:26 +01:00
|
|
|
transaction.begin() # pragma: no cover
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2009-07-13 22:22:37 +02:00
|
|
|
catalog = getToolByName(context, 'portal_catalog')
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2009-07-07 18:16:58 +02:00
|
|
|
def log(msg):
|
2010-12-16 00:52:56 +01:00
|
|
|
# encode string before sending it to external world
|
|
|
|
if isinstance(msg, unicode):
|
2012-01-13 14:40:26 +01:00
|
|
|
msg = msg.encode('utf-8') # pragma: no cover
|
2009-07-07 18:16:58 +02:00
|
|
|
context.plone_log(msg)
|
|
|
|
out.append(msg)
|
|
|
|
|
2012-01-13 14:40:26 +01:00
|
|
|
def migrate_replies(context, in_reply_to, replies,
|
|
|
|
depth=0, just_delete=0):
|
2009-07-13 22:22:37 +02:00
|
|
|
# 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
|
2009-07-12 18:09:34 +02:00
|
|
|
|
2012-06-13 13:17:22 +02:00
|
|
|
workflow = context.portal_workflow
|
|
|
|
oldchain = workflow.getChainForPortalType('Discussion Item')
|
|
|
|
new_workflow = workflow.comment_review_workflow
|
2012-11-28 06:32:07 +01:00
|
|
|
mt = getToolByName(self.context, 'portal_membership')
|
|
|
|
|
2012-06-13 13:17:22 +02:00
|
|
|
if type(oldchain) == TupleType and len(oldchain) > 0:
|
|
|
|
oldchain = oldchain[0]
|
|
|
|
|
|
|
|
for reply in replies:
|
2009-07-12 18:09:34 +02:00
|
|
|
# log
|
2016-02-05 01:39:53 +01:00
|
|
|
indent = ' '
|
2009-07-12 18:09:34 +02:00
|
|
|
for i in range(depth):
|
2016-02-05 01:39:53 +01:00
|
|
|
indent += ' '
|
|
|
|
log('{0}migrate_reply: "{1}".'.format(indent, reply.title))
|
2009-07-12 18:09:34 +02:00
|
|
|
|
2011-04-02 21:33:10 +02:00
|
|
|
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:
|
2012-12-14 06:14:40 +01:00
|
|
|
# create a reply object
|
2011-04-02 21:33:10 +02:00
|
|
|
comment = CommentFactory()
|
|
|
|
comment.title = reply.Title()
|
2011-04-02 21:51:37 +02:00
|
|
|
comment.text = reply.cooked_text
|
|
|
|
comment.mime_type = 'text/html'
|
2011-04-02 21:33:10 +02:00
|
|
|
comment.creator = reply.Creator()
|
2012-12-16 01:47:01 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
comment.author_username = reply.author_username
|
|
|
|
except AttributeError:
|
|
|
|
comment.author_username = reply.Creator()
|
|
|
|
|
2012-11-28 06:32:07 +01:00
|
|
|
member = mt.getMemberById(comment.author_username)
|
2012-12-14 06:14:40 +01:00
|
|
|
if member:
|
|
|
|
comment.author_name = member.fullname
|
|
|
|
|
2012-12-16 01:47:01 +01:00
|
|
|
if not comment.author_name:
|
2013-04-17 14:55:50 +02:00
|
|
|
# In migrated site member.fullname = ''
|
|
|
|
# while member.getProperty('fullname') has the
|
|
|
|
# correct value
|
2013-04-10 09:28:46 +02:00
|
|
|
if member:
|
2013-04-17 14:55:50 +02:00
|
|
|
comment.author_name = member.getProperty(
|
|
|
|
'fullname'
|
|
|
|
)
|
2013-04-10 09:28:46 +02:00
|
|
|
else:
|
|
|
|
comment.author_name = comment.author_username
|
2013-04-17 14:55:50 +02:00
|
|
|
|
2012-12-14 06:14:40 +01:00
|
|
|
try:
|
|
|
|
comment.author_email = reply.email
|
|
|
|
except AttributeError:
|
|
|
|
comment.author_email = None
|
2011-04-02 21:33:10 +02:00
|
|
|
|
2011-04-02 23:26:36 +02:00
|
|
|
comment.creation_date = DT2dt(reply.creation_date)
|
|
|
|
comment.modification_date = DT2dt(reply.modification_date)
|
2011-04-02 21:33:10 +02:00
|
|
|
|
|
|
|
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)
|
2009-07-13 23:01:38 +02:00
|
|
|
|
2012-06-13 13:17:22 +02:00
|
|
|
# migrate the review state
|
|
|
|
old_status = workflow.getStatusOf(oldchain, reply)
|
|
|
|
new_status = {
|
|
|
|
'action': None,
|
|
|
|
'actor': None,
|
|
|
|
'comment': 'Migrated workflow state',
|
2012-10-31 22:46:10 +01:00
|
|
|
'review_state': old_status and old_status.get(
|
2012-06-13 13:17:22 +02:00
|
|
|
'review_state',
|
2013-04-17 14:55:50 +02:00
|
|
|
new_workflow.initial_state
|
|
|
|
) or 'published',
|
2012-06-13 13:17:22 +02:00
|
|
|
'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'])
|
|
|
|
|
2009-07-12 18:09:34 +02:00
|
|
|
self.total_comments_migrated += 1
|
|
|
|
|
|
|
|
# migrate all talkbacks of the reply
|
2012-01-13 14:40:26 +01:00
|
|
|
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)
|
2011-04-02 21:33:10 +02:00
|
|
|
|
2009-07-13 22:22:37 +02:00
|
|
|
if no_replies_left:
|
|
|
|
# remove reply and talkback
|
|
|
|
talkback.deleteReply(reply.id)
|
|
|
|
obj = aq_parent(talkback)
|
|
|
|
obj.talkback = None
|
2016-02-05 01:39:53 +01:00
|
|
|
log('{0}remove {1}'.format(indent, reply.id))
|
2009-07-13 22:22:37 +02:00
|
|
|
self.total_comments_deleted += 1
|
2009-07-12 18:09:34 +02:00
|
|
|
|
2010-12-16 00:52:56 +01:00
|
|
|
# Return True when all comments on a certain level have been
|
2010-08-28 22:18:36 +02:00
|
|
|
# migrated.
|
2009-07-14 00:14:30 +02:00
|
|
|
return True
|
|
|
|
|
2009-07-07 18:16:58 +02:00
|
|
|
# Find content
|
|
|
|
brains = catalog.searchResults(
|
2010-08-28 22:18:36 +02:00
|
|
|
object_provides='Products.CMFCore.interfaces._content.IContentish')
|
2016-02-05 01:39:53 +01:00
|
|
|
log('Found {0} content objects.'.format(len(brains)))
|
2009-07-07 18:16:58 +02:00
|
|
|
|
2013-04-17 14:55:50 +02:00
|
|
|
count_discussion_items = len(
|
|
|
|
catalog.searchResults(Type='Discussion Item')
|
|
|
|
)
|
|
|
|
count_comments_pad = len(
|
|
|
|
catalog.searchResults(object_provides=IComment.__identifier__)
|
|
|
|
)
|
|
|
|
count_comments_old = len(
|
|
|
|
catalog.searchResults(
|
|
|
|
object_provides=IDiscussionResponse.__identifier__
|
|
|
|
)
|
|
|
|
)
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2016-02-05 01:39:53 +01:00
|
|
|
log(
|
|
|
|
'Found {0} Discussion Item objects.'.format(
|
|
|
|
count_discussion_items
|
|
|
|
)
|
|
|
|
)
|
|
|
|
log('Found {0} old discussion items.'.format(count_comments_old))
|
|
|
|
log(
|
|
|
|
'Found {0} plone.app.discussion comments.'.format(
|
|
|
|
count_comments_pad
|
|
|
|
)
|
|
|
|
)
|
2009-12-22 00:36:35 +01:00
|
|
|
|
2016-02-05 01:39:53 +01:00
|
|
|
log('\n')
|
|
|
|
log('Start comment migration.')
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2009-07-13 22:22:37 +02:00
|
|
|
# This loop is necessary to get all contentish objects, but not
|
|
|
|
# the Discussion Items. This wouldn't be necessary if the
|
|
|
|
# zcatalog would support NOT expressions.
|
|
|
|
new_brains = []
|
2009-07-07 18:16:58 +02:00
|
|
|
for brain in brains:
|
|
|
|
if brain.portal_type != 'Discussion Item':
|
2009-07-13 22:22:37 +02:00
|
|
|
new_brains.append(brain)
|
|
|
|
|
|
|
|
# Recursively run through the comment tree and migrate all comments.
|
|
|
|
for brain in new_brains:
|
|
|
|
obj = brain.getObject()
|
2012-01-13 14:40:26 +01:00
|
|
|
talkback = getattr(obj, 'talkback', None)
|
2009-07-13 22:22:37 +02:00
|
|
|
if talkback:
|
|
|
|
replies = talkback.getReplies()
|
|
|
|
if replies:
|
|
|
|
conversation = IConversation(obj)
|
2016-02-05 01:39:53 +01:00
|
|
|
log('\n')
|
|
|
|
log(
|
|
|
|
'Migrate "{0}" ({1})'.format(
|
|
|
|
obj.Title(),
|
|
|
|
obj.absolute_url(relative=1)
|
|
|
|
)
|
|
|
|
)
|
2009-07-13 22:22:37 +02:00
|
|
|
migrate_replies(context, 0, replies)
|
|
|
|
obj = aq_parent(talkback)
|
|
|
|
obj.talkback = None
|
|
|
|
|
|
|
|
if self.total_comments_deleted != self.total_comments_migrated:
|
2013-04-17 14:55:50 +02:00
|
|
|
log(
|
2016-02-05 01:39:53 +01:00
|
|
|
'Something went wrong during migration. The number of '
|
|
|
|
'migrated comments ({0}) differs from the number of deleted '
|
|
|
|
'comments ({1}).'.format(
|
2013-04-17 14:55:50 +02:00
|
|
|
self.total_comments_migrated,
|
|
|
|
self.total_comments_deleted
|
|
|
|
)
|
|
|
|
)
|
2010-09-20 12:03:55 +02:00
|
|
|
if not test: # pragma: no cover
|
2012-01-13 14:40:26 +01:00
|
|
|
transaction.abort() # pragma: no cover
|
2016-02-05 01:39:53 +01:00
|
|
|
log('Abort transaction') # pragma: no cover
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2016-02-05 01:39:53 +01:00
|
|
|
log('\n')
|
|
|
|
log('Comment migration finished.')
|
|
|
|
log('\n')
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2016-02-05 01:39:53 +01:00
|
|
|
log(
|
|
|
|
'{0} of {1} comments migrated.'.format(
|
|
|
|
self.total_comments_migrated,
|
|
|
|
count_comments_old
|
|
|
|
)
|
|
|
|
)
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2010-01-02 20:45:05 +01:00
|
|
|
if self.total_comments_migrated != count_comments_old:
|
2013-04-17 14:55:50 +02:00
|
|
|
log(
|
2016-02-05 01:39:53 +01:00
|
|
|
'{0} comments could not be migrated.'.format(
|
2013-04-17 14:55:50 +02:00
|
|
|
count_comments_old - self.total_comments_migrated
|
|
|
|
)
|
|
|
|
) # pragma: no cover
|
2016-02-05 01:39:53 +01:00
|
|
|
log('Please make sure your ' +
|
|
|
|
'portal catalog is up-to-date.') # pragma: no cover
|
2010-12-16 00:52:56 +01:00
|
|
|
|
2010-01-03 18:36:01 +01:00
|
|
|
if dry_run and not test:
|
2012-01-13 14:40:26 +01:00
|
|
|
transaction.abort() # pragma: no cover
|
2016-02-05 01:39:53 +01:00
|
|
|
log('Dry run') # pragma: no cover
|
|
|
|
log('Abort transaction') # pragma: no cover
|
2010-01-03 18:36:01 +01:00
|
|
|
if not test:
|
2012-01-13 14:40:26 +01:00
|
|
|
transaction.commit() # pragma: no cover
|
2009-07-12 18:09:34 +02:00
|
|
|
return '\n'.join(out)
|