diff --git a/CHANGES.rst b/CHANGES.rst index c44d6d4..cf7a295 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,7 +14,8 @@ New features: Bug fixes: -- *add item here* +- Add Python 2 / 3 compatibility + [pbauer] 3.0.4 (2017-11-24) diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py index 8961316..c817517 100644 --- a/plone/app/discussion/browser/comments.py +++ b/plone/app/discussion/browser/comments.py @@ -33,6 +33,9 @@ from zope.i18nmessageid import Message from zope.interface import alsoProvides +import six + + COMMENT_DESCRIPTION_PLAIN_TEXT = _( u'comment_description_plain_text', default=u'You can add a comment by filling out the form below. ' @@ -152,11 +155,11 @@ class CommentForm(extensible.ExtensibleForm, form.Form): if 'author_name' in data: author_name = data['author_name'] if isinstance(author_name, str): - author_name = unicode(author_name, 'utf-8') + author_name = six.text_type(author_name, 'utf-8') if 'author_email' in data: author_email = data['author_email'] if isinstance(author_email, str): - author_email = unicode(author_email, 'utf-8') + author_email = six.text_type(author_email, 'utf-8') # Set comment author properties for anonymous users or members portal_membership = getToolByName(context, 'portal_membership') @@ -171,10 +174,10 @@ class CommentForm(extensible.ExtensibleForm, form.Form): if not fullname or fullname == '': fullname = member.getUserName() elif isinstance(fullname, str): - fullname = unicode(fullname, 'utf-8') + fullname = six.text_type(fullname, 'utf-8') author_name = fullname if email and isinstance(email, str): - email = unicode(email, 'utf-8') + email = six.text_type(email, 'utf-8') # XXX: according to IComment interface author_email must not be # set for logged in users, cite: # 'for anonymous comments only, set to None for logged in comments' @@ -427,7 +430,7 @@ class CommentsViewlet(ViewletBase): """ if self.get_replies(workflow_actions) is not None: try: - self.get_replies(workflow_actions).next() + next(self.get_replies(workflow_actions)) return True except StopIteration: # pragma: no cover pass diff --git a/plone/app/discussion/comment.py b/plone/app/discussion/comment.py index 5a7f689..33d77f4 100644 --- a/plone/app/discussion/comment.py +++ b/plone/app/discussion/comment.py @@ -40,6 +40,9 @@ from zope.interface import implementer import logging +import six + + COMMENT_TITLE = _( u'comment_title', default=u'${author_name} on ${content}', @@ -122,7 +125,7 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable, @property def __name__(self): - return self.comment_id and unicode(self.comment_id) or None + return self.comment_id and six.text_type(self.comment_id) or None @property def id(self): @@ -147,7 +150,7 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable, text = self.text if text is None: return '' - if isinstance(text, unicode): + if isinstance(text, six.text_type): text = text.encode('utf8') transform = transforms.convertTo( targetMimetype, @@ -238,7 +241,7 @@ def notify_content_object_deleted(obj, event): if IAnnotatable.providedBy(obj): conversation = IConversation(obj) while conversation: - del conversation[conversation.keys()[0]] + del conversation[list(conversation.keys())[0]] def notify_comment_added(obj, event): @@ -427,7 +430,7 @@ def notify_moderator(obj, event): # Send email try: mail_host.send(message, mto, sender, subject, charset='utf-8') - except SMTPException, e: + except SMTPException as e: logger.error('SMTP exception (%s) while trying to send an ' + 'email notification to the comment moderator ' + '(from %s to %s, message: %s)', diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py index 4963cce..e7dec30 100644 --- a/plone/app/discussion/conversation.py +++ b/plone/app/discussion/conversation.py @@ -40,6 +40,9 @@ from zope.lifecycleevent import ObjectRemovedEvent import time +import six + + @implementer(IConversation, IHideFromBreadcrumbs) class Conversation(Traversable, Persistent, Explicit): """A conversation is a container for all comments on a content object. @@ -110,7 +113,7 @@ class Conversation(Traversable, Persistent, Explicit): def getComments(self, start=0, size=None): """Get unthreaded comments """ - count = 0l + count = 0 for comment in self._comments.values(min=start): # Yield the acquisition wrapped comment yield self[comment.id] @@ -138,7 +141,7 @@ class Conversation(Traversable, Persistent, Explicit): # Find top level threads comments = self._children.get(root, None) if comments is not None: - count = 0l + count = 0 for comment_id in comments.keys(min=start): # Abort if we have found all the threads we want @@ -274,14 +277,14 @@ class Conversation(Traversable, Persistent, Explicit): return [v.__of__(self) for v in self._comments.values()] def iterkeys(self): - return self._comments.iterkeys() + return six.iterkeys(self._comments) def itervalues(self): - for v in self._comments.itervalues(): + for v in six.itervalues(self._comments): yield v.__of__(self) def iteritems(self): - for k, v in self._comments.iteritems(): + for k, v in six.iteritems(self._comments): yield (k, v.__of__(self),) def allowedContentTypes(self): @@ -334,7 +337,7 @@ class ConversationReplies(object): def __init__(self, context): self.conversation = context - self.comment_id = 0l + self.comment_id = 0 def addComment(self, comment): comment.in_reply_to = None diff --git a/plone/app/discussion/tests/test_comments_viewlet.py b/plone/app/discussion/tests/test_comments_viewlet.py index 71f2c49..630b237 100644 --- a/plone/app/discussion/tests/test_comments_viewlet.py +++ b/plone/app/discussion/tests/test_comments_viewlet.py @@ -565,8 +565,8 @@ class TestCommentsViewlet(unittest.TestCase): replies = self.viewlet.get_replies() self.assertEqual(len(tuple(replies)), 2) replies = self.viewlet.get_replies() - replies.next() - replies.next() + next(replies) + next(replies) self.assertRaises(StopIteration, replies.next) def test_get_replies_on_non_annotatable_object(self): @@ -593,7 +593,7 @@ class TestCommentsViewlet(unittest.TestCase): ('comment_review_workflow,') ) # Check if workflow actions are available - reply = self.viewlet.get_replies(workflow_actions=True).next() + reply = next(self.viewlet.get_replies(workflow_actions=True)) self.assertTrue('actions' in reply) self.assertEqual( reply['actions'][0]['id'], diff --git a/plone/app/discussion/tests/test_conversation.py b/plone/app/discussion/tests/test_conversation.py index c2d30d0..4b4e30b 100644 --- a/plone/app/discussion/tests/test_conversation.py +++ b/plone/app/discussion/tests/test_conversation.py @@ -22,6 +22,9 @@ from zope.component import queryUtility import unittest +import six + + try: from plone.dexterity.interfaces import IDexterityContent DEXTERITY = True @@ -383,17 +386,17 @@ class ConversationTest(unittest.TestCase): self.assertTrue(comment2 in conversation.values()) # check if comment ids are in iterkeys - self.assertTrue(new_id1 in conversation.iterkeys()) - self.assertTrue(new_id2 in conversation.iterkeys()) - self.assertFalse(123 in conversation.iterkeys()) + self.assertTrue(new_id1 in six.iterkeys(conversation)) + self.assertTrue(new_id2 in six.iterkeys(conversation)) + self.assertFalse(123 in six.iterkeys(conversation)) # check if comment objects are in itervalues - self.assertTrue(comment1 in conversation.itervalues()) - self.assertTrue(comment2 in conversation.itervalues()) + self.assertTrue(comment1 in six.itervalues(conversation)) + self.assertTrue(comment2 in six.itervalues(conversation)) # check if iteritems returns (key, comment object) pairs - self.assertTrue((new_id1, comment1) in conversation.iteritems()) - self.assertTrue((new_id2, comment2) in conversation.iteritems()) + self.assertTrue((new_id1, comment1) in six.iteritems(conversation)) + self.assertTrue((new_id2, comment2) in six.iteritems(conversation)) # TODO test acquisition wrapping # self.assertTrue(aq_base(aq_parent(comment1)) is conversation) diff --git a/plone/app/discussion/tests/test_moderation_view.py b/plone/app/discussion/tests/test_moderation_view.py index c84b3e9..f0ac2f9 100644 --- a/plone/app/discussion/tests/test_moderation_view.py +++ b/plone/app/discussion/tests/test_moderation_view.py @@ -156,7 +156,7 @@ class ModerationBulkActionsViewTest(unittest.TestCase): # Make sure that the two comments have been deleted self.assertEqual(len(self.conversation.objectIds()), 1) - comment = self.conversation.getComments().next() + comment = next(self.conversation.getComments()) self.assertTrue(comment) self.assertEqual(comment, self.comment2) diff --git a/setup.py b/setup.py index 8c16f9c..953d398 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ install_requires = [ 'plone.registry', 'plone.z3cform', 'Products.CMFPlone', + 'six', 'ZODB3', 'zope.interface', 'zope.component',