Merge pull request #133 from plone/python3

Python3
This commit is contained in:
Philip Bauer 2018-09-21 10:03:37 +02:00 committed by GitHub
commit 3e2446c6b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 45 additions and 48 deletions

View File

@ -1,7 +1,6 @@
Changelog Changelog
========= =========
3.0.6 (unreleased)
3.0.7 (unreleased) 3.0.7 (unreleased)
------------------ ------------------
@ -23,25 +22,19 @@ Bug fixes:
This is a follow up to `issue 476 <https://github.com/plone/plone.app.contenttypes/issues/476>`_. This is a follow up to `issue 476 <https://github.com/plone/plone.app.contenttypes/issues/476>`_.
[iham] [iham]
- Fix commenting and tests in python 3.
[pbauer]
3.0.6 (2018-06-18) 3.0.6 (2018-06-18)
------------------ ------------------
Breaking changes:
- *add item here*
New features:
- *add item here*
Bug fixes: Bug fixes:
- Fix tests to work with merges plone.login. - Fix tests to work with merges plone.login.
[jensens] [jensens]
- More Python 2 / 3 compatibility. - More Python 2 / 3 compatibility.
[pbauer] [pbauer, hvelarde]
3.0.5 (2018-02-04) 3.0.5 (2018-02-04)

View File

@ -17,6 +17,7 @@ from plone.z3cform import z2
from plone.z3cform.fieldsets import extensible from plone.z3cform.fieldsets import extensible
from plone.z3cform.interfaces import IWrappedForm from plone.z3cform.interfaces import IWrappedForm
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.statusmessages.interfaces import IStatusMessage from Products.statusmessages.interfaces import IStatusMessage
from six.moves.urllib.parse import quote from six.moves.urllib.parse import quote
@ -32,8 +33,6 @@ from zope.i18n import translate
from zope.i18nmessageid import Message from zope.i18nmessageid import Message
from zope.interface import alsoProvides from zope.interface import alsoProvides
import six
COMMENT_DESCRIPTION_PLAIN_TEXT = _( COMMENT_DESCRIPTION_PLAIN_TEXT = _(
u'comment_description_plain_text', u'comment_description_plain_text',
@ -152,13 +151,9 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
# Make sure author_name/ author_email is properly encoded # Make sure author_name/ author_email is properly encoded
if 'author_name' in data: if 'author_name' in data:
author_name = data['author_name'] author_name = safe_unicode(data['author_name'])
if isinstance(author_name, str):
author_name = six.text_type(author_name, 'utf-8')
if 'author_email' in data: if 'author_email' in data:
author_email = data['author_email'] author_email = safe_unicode(data['author_email'])
if isinstance(author_email, str):
author_email = six.text_type(author_email, 'utf-8')
# Set comment author properties for anonymous users or members # Set comment author properties for anonymous users or members
portal_membership = getToolByName(context, 'portal_membership') portal_membership = getToolByName(context, 'portal_membership')
@ -167,16 +162,13 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
'Reply to item', context): 'Reply to item', context):
# Member # Member
member = portal_membership.getAuthenticatedMember() member = portal_membership.getAuthenticatedMember()
# memberdata is stored as utf-8 encoded strings email = safe_unicode(member.getProperty('email'))
email = member.getProperty('email')
fullname = member.getProperty('fullname') fullname = member.getProperty('fullname')
if not fullname or fullname == '': if not fullname or fullname == '':
fullname = member.getUserName() fullname = member.getUserName()
elif isinstance(fullname, str): fullname = safe_unicode(fullname)
fullname = six.text_type(fullname, 'utf-8')
author_name = fullname author_name = fullname
if email and isinstance(email, str): email = safe_unicode(email)
email = six.text_type(email, 'utf-8')
# XXX: according to IComment interface author_email must not be # noqa T000 # XXX: according to IComment interface author_email must not be # noqa T000
# set for logged in users, cite: # set for logged in users, cite:
# 'for anonymous comments only, set to None for logged in comments' # 'for anonymous comments only, set to None for logged in comments'

View File

@ -13,6 +13,7 @@ from Products.CMFCore.interfaces import IContentish
from Products.CMFPlone.utils import safe_unicode from Products.CMFPlone.utils import safe_unicode
from Products.ZCatalog.interfaces import IZCatalog from Products.ZCatalog.interfaces import IZCatalog
import six
MAX_DESCRIPTION = 25 MAX_DESCRIPTION = 25
@ -70,7 +71,12 @@ def title(object):
@indexer(IComment) @indexer(IComment)
def creator(object): def creator(object):
return object.creator and safe_unicode(object.creator).encode('utf-8') if not object.creator:
return
value = safe_unicode(object.creator)
if six.PY2:
return value.encode('utf8')
return value
@indexer(IComment) @indexer(IComment)

View File

@ -148,7 +148,7 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
text = self.text text = self.text
if text is None: if text is None:
return '' return ''
if isinstance(text, six.text_type): if six.PY2 and isinstance(text, six.text_type):
text = text.encode('utf8') text = text.encode('utf8')
transform = transforms.convertTo( transform = transforms.convertTo(
targetMimetype, targetMimetype,

View File

@ -161,7 +161,7 @@ class Conversation(Traversable, Persistent, Explicit):
comment = aq_base(comment) comment = aq_base(comment)
id = long(time.time() * 1e6) id = int(time.time() * 1e6)
while id in self._comments: while id in self._comments:
id += 1 id += 1
@ -206,22 +206,22 @@ class Conversation(Traversable, Persistent, Explicit):
return len(self._comments) return len(self._comments)
def __contains__(self, key): def __contains__(self, key):
return long(key) in self._comments return int(key) in self._comments
def __getitem__(self, key): def __getitem__(self, key):
"""Get an item by its long key """Get an item by its int key
""" """
try: try:
comment_id = long(key) comment_id = int(key)
except ValueError: except ValueError:
return return
return self._comments[comment_id].__of__(self) return self._comments[comment_id].__of__(self)
def __delitem__(self, key, suppress_container_modified=False): def __delitem__(self, key, suppress_container_modified=False):
"""Delete an item by its long key """Delete an item by its int key
""" """
key = long(key) key = int(key)
comment = self[key].__of__(self) comment = self[key].__of__(self)
commentator = comment.author_username commentator = comment.author_username
@ -260,7 +260,7 @@ class Conversation(Traversable, Persistent, Explicit):
return iter(self._comments) return iter(self._comments)
def get(self, key, default=None): def get(self, key, default=None):
comment = self._comments.get(long(key), default) comment = self._comments.get(int(key), default)
if comment is default: if comment is default:
return default return default
return comment.__of__(self) return comment.__of__(self)
@ -347,20 +347,20 @@ class ConversationReplies(object):
return len(self.children) return len(self.children)
def __contains__(self, key): def __contains__(self, key):
return long(key) in self.children return int(key) in self.children
def __getitem__(self, key): def __getitem__(self, key):
"""Get an item by its long key """Get an item by its int key
""" """
key = long(key) key = int(key)
if key not in self.children: if key not in self.children:
raise KeyError(key) raise KeyError(key)
return self.conversation[key] return self.conversation[key]
def __delitem__(self, key): def __delitem__(self, key):
"""Delete an item by its long key """Delete an item by its int key
""" """
key = long(key) key = int(key)
if key not in self.children: if key not in self.children:
raise KeyError(key) raise KeyError(key)
del self.conversation[key] del self.conversation[key]
@ -369,7 +369,7 @@ class ConversationReplies(object):
return iter(self.children) return iter(self.children)
def get(self, key, default=None): def get(self, key, default=None):
key = long(key) key = int(key)
if key not in self.children: if key not in self.children:
return default return default
return self.conversation.get(key) return self.conversation.get(key)

View File

@ -470,7 +470,7 @@ Edit the content object.
>>> from hashlib import sha1 as sha >>> from hashlib import sha1 as sha
>>> ring = _getKeyring('foo') >>> ring = _getKeyring('foo')
>>> secret = ring.random() >>> secret = ring.random()
>>> token = hmac.new(secret, 'admin', sha).hexdigest() >>> token = hmac.new(secret.encode('utf8'), b'admin', sha).hexdigest()
>>> browser.open("http://nohost/plone/doc1/edit?_authenticator=" + token) >>> browser.open("http://nohost/plone/doc1/edit?_authenticator=" + token)
>>> browser.getControl(name='form.widgets.IRichTextBehavior.text').value = "Lorem ipsum" >>> browser.getControl(name='form.widgets.IRichTextBehavior.text').value = "Lorem ipsum"
>>> browser.getControl('Save').click() >>> browser.getControl('Save').click()

View File

@ -54,7 +54,7 @@ class CommentTest(unittest.TestCase):
difference = difference.seconds difference = difference.seconds
# We hope that between comment1 and local_utc happen less than # We hope that between comment1 and local_utc happen less than
# 10 seconds # 10 seconds
self.assertFalse(difference / 10) self.assertFalse(difference // 10)
def test_id(self): def test_id(self):
comment1 = createObject('plone.Comment') comment1 = createObject('plone.Comment')
@ -171,9 +171,10 @@ class CommentTest(unittest.TestCase):
def test_getText_with_non_ascii_characters(self): def test_getText_with_non_ascii_characters(self):
comment1 = createObject('plone.Comment') comment1 = createObject('plone.Comment')
comment1.text = u'Umlaute sind ä, ö und ü.' comment1.text = u'Umlaute sind ä, ö und ü.'
out = b'<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>'
self.assertEqual( self.assertEqual(
comment1.getText(), comment1.getText(),
'<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>', out.decode('utf8')
) )
def test_getText_doesnt_link(self): def test_getText_doesnt_link(self):

View File

@ -567,7 +567,8 @@ class TestCommentsViewlet(unittest.TestCase):
replies = self.viewlet.get_replies() replies = self.viewlet.get_replies()
next(replies) next(replies)
next(replies) next(replies)
self.assertRaises(StopIteration, replies.next) with self.assertRaises(StopIteration):
next(replies)
def test_get_replies_on_non_annotatable_object(self): def test_get_replies_on_non_annotatable_object(self):
context = self.portal.MailHost # the mail host is not annotatable context = self.portal.MailHost # the mail host is not annotatable
@ -575,7 +576,8 @@ class TestCommentsViewlet(unittest.TestCase):
replies = viewlet.get_replies() replies = viewlet.get_replies()
self.assertEqual(len(tuple(replies)), 0) self.assertEqual(len(tuple(replies)), 0)
replies = viewlet.get_replies() replies = viewlet.get_replies()
self.assertRaises(StopIteration, replies.next) with self.assertRaises(StopIteration):
next(replies)
def test_get_replies_with_workflow_actions(self): def test_get_replies_with_workflow_actions(self):
self.assertFalse(self.viewlet.get_replies(workflow_actions=True)) self.assertFalse(self.viewlet.get_replies(workflow_actions=True))

View File

@ -54,7 +54,7 @@ class CommentContentRulesTest(unittest.TestCase):
def testCommentIdStringSubstitution(self): def testCommentIdStringSubstitution(self):
comment_id = getAdapter(self.document, IStringSubstitution, comment_id = getAdapter(self.document, IStringSubstitution,
name=u'comment_id') name=u'comment_id')
self.assertIsInstance(comment_id(), long) self.assertIsInstance(comment_id(), int)
def testCommentTextStringSubstitution(self): def testCommentTextStringSubstitution(self):
comment_text = getAdapter(self.document, IStringSubstitution, comment_text = getAdapter(self.document, IStringSubstitution,
@ -114,7 +114,7 @@ class ReplyContentRulesTest(unittest.TestCase):
IStringSubstitution, IStringSubstitution,
name=u'comment_id', name=u'comment_id',
) )
self.assertIsInstance(reply_id(), long) self.assertIsInstance(reply_id(), int)
def testReplyTextStringSubstitution(self): def testReplyTextStringSubstitution(self):
reply_text = getAdapter( reply_text = getAdapter(

View File

@ -68,7 +68,7 @@ class ConversationTest(unittest.TestCase):
new_id = conversation.addComment(comment) new_id = conversation.addComment(comment)
# Check that the conversation methods return the correct data # Check that the conversation methods return the correct data
self.assertTrue(isinstance(comment.comment_id, long)) self.assertTrue(isinstance(comment.comment_id, int))
self.assertTrue(IComment.providedBy(conversation[new_id])) self.assertTrue(IComment.providedBy(conversation[new_id]))
self.assertEqual( self.assertEqual(
aq_base(conversation[new_id].__parent__), aq_base(conversation[new_id].__parent__),
@ -641,7 +641,7 @@ class ConversationTest(unittest.TestCase):
def test_unconvertible_id(self): def test_unconvertible_id(self):
# make sure the conversation view doesn't break when given comment id # make sure the conversation view doesn't break when given comment id
# can't be converted to long # can't be converted to int
conversation = self.portal.doc1.restrictedTraverse( conversation = self.portal.doc1.restrictedTraverse(
'++conversation++default/ThisCantBeRight', '++conversation++default/ThisCantBeRight',

View File

@ -37,9 +37,12 @@ setup(name='plone.app.discussion',
'Framework :: Plone', 'Framework :: Plone',
'Framework :: Plone :: 5.0', 'Framework :: Plone :: 5.0',
'Framework :: Plone :: 5.1', 'Framework :: Plone :: 5.1',
'Framework :: Plone :: 5.2',
'License :: OSI Approved :: GNU General Public License (GPL)', 'License :: OSI Approved :: GNU General Public License (GPL)',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
], ],
keywords='plone discussion', keywords='plone discussion',
author='Timo Stollenwerk - Plone Foundation', author='Timo Stollenwerk - Plone Foundation',