Skip to content

Commit

Permalink
Merge pull request #249 from collective/maurits-pad-optional
Browse files Browse the repository at this point in the history
Load code for exporting/importing comments conditionally.
  • Loading branch information
pbauer authored Jan 7, 2025
2 parents 757c0ff + 181e53a commit bcb7f7f
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 126 deletions.
1 change: 0 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ jobs:
- ["3.6", "plone52-py36"]
- ["3.7", "plone52-py37"]
- ["3.8", "plone52-py38"]
- ["3.8", "plone60-py38"]
- ["3.9", "plone60-py39"]
- ["3.10", "plone60-py310"]
- ["3.11", "plone60-py311"]
Expand Down
6 changes: 5 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ Changelog
1.13 (unreleased)
-----------------

- Load code for exporting/importing comments conditionally.
``plone.app.discussion`` is optional since Plone 6.1.
[maurits]

- Add and run a black version, that is compatible with Python 2.
[pgrunewald]

- Fix ``AtributeError: 'NamedImage' object has no attribute '_blob'`` similar to #236.
[petschki]

Expand Down
2 changes: 2 additions & 0 deletions src/collective/exportimport/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
/>

<browser:page
zcml:condition="installed plone.app.discussion"
name="export_discussion"
for="zope.interface.Interface"
class=".export_other.ExportDiscussion"
Expand Down Expand Up @@ -234,6 +235,7 @@
/>

<browser:page
zcml:condition="installed plone.app.discussion"
name="import_discussion"
for="zope.interface.Interface"
class=".import_other.ImportDiscussion"
Expand Down
88 changes: 49 additions & 39 deletions src/collective/exportimport/export_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from OFS.interfaces import IOrderedContainer
from operator import itemgetter
from plone import api
from plone.app.discussion.interfaces import IConversation
from plone.app.portlets.interfaces import IPortletTypeInterface
from plone.app.redirector.interfaces import IRedirectionStorage
from plone.app.textfield.value import RichTextValue
Expand Down Expand Up @@ -69,6 +68,15 @@
except pkg_resources.DistributionNotFound:
IS_PAM_1 = False

try:
pkg_resources.get_distribution("plone.app.discussion")
except pkg_resources.DistributionNotFound:
HAS_DISCUSSION = False
IConversation = None
else:
HAS_DISCUSSION = True
from plone.app.discussion.interfaces import IConversation


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -588,45 +596,47 @@ def get_default_page_info(self, obj):
}


class ExportDiscussion(BaseExport):
def __call__(self, download_to_server=False):
self.title = _(u"Export comments")
self.download_to_server = download_to_server
if not self.request.form.get("form.submitted", False):
return self.index()

logger.info(u"Exporting discussions...")
data = self.all_discussions()
logger.info(u"Exported %s discussions", len(data))
self.download(data)

def all_discussions(self):
results = []
for brain in api.content.find(
object_provides=IContentish.__identifier__,
sort_on="path",
context=self.context,
):
try:
obj = brain.getObject()
if obj is None:
logger.error(u"brain.getObject() is None %s", brain.getPath())
continue
conversation = IConversation(obj, None)
if not conversation:
if HAS_DISCUSSION: # noqa: C901

class ExportDiscussion(BaseExport):
def __call__(self, download_to_server=False):
self.title = _(u"Export comments")
self.download_to_server = download_to_server
if not self.request.form.get("form.submitted", False):
return self.index()

logger.info(u"Exporting discussions...")
data = self.all_discussions()
logger.info(u"Exported %s discussions", len(data))
self.download(data)

def all_discussions(self):
results = []
for brain in api.content.find(
object_provides=IContentish.__identifier__,
sort_on="path",
context=self.context,
):
try:
obj = brain.getObject()
if obj is None:
logger.error(u"brain.getObject() is None %s", brain.getPath())
continue
conversation = IConversation(obj, None)
if not conversation:
continue
serializer = getMultiAdapter(
(conversation, self.request), ISerializeToJson
)
output = serializer()
if output:
results.append({"uuid": IUUID(obj), "conversation": output})
except Exception:
logger.info(
"Error exporting comments for %s", brain.getURL(), exc_info=True
)
continue
serializer = getMultiAdapter(
(conversation, self.request), ISerializeToJson
)
output = serializer()
if output:
results.append({"uuid": IUUID(obj), "conversation": output})
except Exception:
logger.info(
"Error exporting comments for %s", brain.getURL(), exc_info=True
)
continue
return results
return results


class ExportPortlets(BaseExport):
Expand Down
182 changes: 97 additions & 85 deletions src/collective/exportimport/import_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
from operator import itemgetter
from collective.exportimport.export_other import PORTAL_PLACEHOLDER
from plone import api
from plone.app.discussion.comment import Comment
from plone.app.discussion.interfaces import IConversation
from plone.app.portlets.interfaces import IPortletTypeInterface
from plone.app.redirector.interfaces import IRedirectionStorage
from plone.portlets.interfaces import ILocalPortletAssignmentManager
Expand All @@ -33,6 +31,7 @@
import dateutil
import json
import logging
import pkg_resources
import six
import transaction

Expand Down Expand Up @@ -64,6 +63,17 @@
else:
from html import unescape

try:
pkg_resources.get_distribution("plone.app.discussion")
except pkg_resources.DistributionNotFound:
HAS_DISCUSSION = False
Comment = None
IConversation = None
else:
HAS_DISCUSSION = True
from plone.app.discussion.comment import Comment
from plone.app.discussion.interfaces import IConversation


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -563,97 +573,99 @@ def import_default_pages(self, data):
return results


class ImportDiscussion(BrowserView):
"""Import default pages"""
if HAS_DISCUSSION: # noqa: C901

def __call__(self, jsonfile=None, return_json=False):
if jsonfile:
self.portal = api.portal.get()
status = "success"
try:
if isinstance(jsonfile, str):
return_json = True
data = json.loads(jsonfile)
elif isinstance(jsonfile, FileUpload):
data = json.loads(jsonfile.read())
else:
raise ("Data is neither text nor upload.")
except Exception as e:
status = "error"
logger.error(e)
msg = _(u"Failure while uploading: {}").format(e)
api.portal.show_message(msg, request=self.request)
else:
results = self.import_data(data)
msg = _(u"Imported {} comments").format(results)
api.portal.show_message(msg, self.request)
if return_json:
msg = {"state": status, "msg": msg}
return json.dumps(msg)
class ImportDiscussion(BrowserView):
"""Import discussions / comments"""

return self.index()
def __call__(self, jsonfile=None, return_json=False):
if jsonfile:
self.portal = api.portal.get()
status = "success"
try:
if isinstance(jsonfile, str):
return_json = True
data = json.loads(jsonfile)
elif isinstance(jsonfile, FileUpload):
data = json.loads(jsonfile.read())
else:
raise ("Data is neither text nor upload.")
except Exception as e:
status = "error"
logger.error(e)
msg = _(u"Failure while uploading: {}").format(e)
api.portal.show_message(msg, request=self.request)
else:
results = self.import_data(data)
msg = _(u"Imported {} comments").format(results)
api.portal.show_message(msg, self.request)
if return_json:
msg = {"state": status, "msg": msg}
return json.dumps(msg)

def import_data(self, data):
results = 0
for conversation_data in data:
obj = api.content.get(UID=conversation_data["uuid"])
if not obj:
continue
added = 0
conversation = IConversation(obj)
return self.index()

for item in conversation_data["conversation"]["items"]:
def import_data(self, data):
results = 0
for conversation_data in data:
obj = api.content.get(UID=conversation_data["uuid"])
if not obj:
continue
added = 0
conversation = IConversation(obj)

if isinstance(item["text"], dict) and item["text"].get("data"):
item["text"] = item["text"]["data"]
for item in conversation_data["conversation"]["items"]:

comment = Comment()
comment_id = int(item["comment_id"])
comment.comment_id = comment_id
comment.creation_date = dateutil.parser.parse(item["creation_date"])
comment.modification_date = dateutil.parser.parse(
item["modification_date"]
)
comment.author_name = item["author_name"]
comment.author_username = item["author_username"]
comment.creator = item["author_username"]
comment.text = unescape(
item["text"]
.replace(u"\r<br />", u"\r\n")
.replace(u"<br />", u"\r\n")
)
if isinstance(item["text"], dict) and item["text"].get("data"):
item["text"] = item["text"]["data"]

if item["user_notification"]:
comment.user_notification = True
if item.get("in_reply_to"):
comment.in_reply_to = int(item["in_reply_to"])

conversation._comments[comment_id] = comment
comment.__parent__ = aq_base(conversation)
commentator = comment.author_username
if commentator:
if commentator not in conversation._commentators:
conversation._commentators[commentator] = 0
conversation._commentators[commentator] += 1

reply_to = comment.in_reply_to
if not reply_to:
# top level comments are in reply to the faux id 0
comment.in_reply_to = reply_to = 0

if reply_to not in conversation._children:
conversation._children[reply_to] = LLSet()
conversation._children[reply_to].insert(comment_id)

# Add the annotation if not already done
annotions = IAnnotations(obj)
if DISCUSSION_ANNOTATION_KEY not in annotions:
annotions[DISCUSSION_ANNOTATION_KEY] = aq_base(conversation)
added += 1
logger.info("Added {} comments to {}".format(added, obj.absolute_url()))
results += added
comment = Comment()
comment_id = int(item["comment_id"])
comment.comment_id = comment_id
comment.creation_date = dateutil.parser.parse(item["creation_date"])
comment.modification_date = dateutil.parser.parse(
item["modification_date"]
)
comment.author_name = item["author_name"]
comment.author_username = item["author_username"]
comment.creator = item["author_username"]
comment.text = unescape(
item["text"]
.replace(u"\r<br />", u"\r\n")
.replace(u"<br />", u"\r\n")
)

return results
if item["user_notification"]:
comment.user_notification = True
if item.get("in_reply_to"):
comment.in_reply_to = int(item["in_reply_to"])

conversation._comments[comment_id] = comment
comment.__parent__ = aq_base(conversation)
commentator = comment.author_username
if commentator:
if commentator not in conversation._commentators:
conversation._commentators[commentator] = 0
conversation._commentators[commentator] += 1

reply_to = comment.in_reply_to
if not reply_to:
# top level comments are in reply to the faux id 0
comment.in_reply_to = reply_to = 0

if reply_to not in conversation._children:
conversation._children[reply_to] = LLSet()
conversation._children[reply_to].insert(comment_id)

# Add the annotation if not already done
annotions = IAnnotations(obj)
if DISCUSSION_ANNOTATION_KEY not in annotions:
annotions[DISCUSSION_ANNOTATION_KEY] = aq_base(conversation)
added += 1
logger.info("Added {} comments to {}".format(added, obj.absolute_url()))
results += added

return results


class ImportPortlets(BrowserView):
Expand Down

0 comments on commit bcb7f7f

Please sign in to comment.