diff --git a/last_commit.txt b/last_commit.txt index 383627a3e3..dbd731d85d 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,121 +1,120 @@ -Repository: plone.folder +Repository: plone.autoform Branch: refs/heads/master -Date: 2018-09-18T15:52:53+02:00 +Date: 2018-09-18T15:24:26+02:00 Author: Philip Bauer (pbauer) -Commit: https://github.com/plone/plone.folder/commit/4eb1dd4363224d44d0e52d57827d87aa1a65bb93 +Commit: https://github.com/plone/plone.autoform/commit/a7a9ca54c69747f576e1751b0e129b5d1ca4a31f -fix ordering of content in folder in py3 -fix all test in py3 +fix sorting when group.order is None Files changed: -M src/plone/folder/default.py -M src/plone/folder/partial.py -M src/plone/folder/tests/test_dict_interface.py -M src/plone/folder/tests/test_integration.py +M plone/autoform/base.py -b'diff --git a/src/plone/folder/default.py b/src/plone/folder/default.py\nindex c7ad8d7..266498d 100644\n--- a/src/plone/folder/default.py\n+++ b/src/plone/folder/default.py\n@@ -136,8 +136,9 @@ def keyfn(obj_id):\n if callable(attr):\n return attr()\n return attr\n- order.sort(None, keyfn, bool(reverse))\n-\n+ # order.sort(cmd=None, key=keyfn, reverse=bool(reverse))\n+ order = sorted(order, key=keyfn, reverse=bool(reverse))\n+ self._set_order(order)\n for n, obj_id in enumerate(order):\n pos[obj_id] = n\n return -1\n@@ -165,6 +166,13 @@ def _order(self, create=False):\n return annotations.setdefault(self.ORDER_KEY, PersistentList())\n return annotations.get(self.ORDER_KEY, [])\n \n+ def _set_order(self, value):\n+ # We added a setter because in py2 _order is modified inplace\n+ # with .sort() while in py3 we sort with sorted and thus need to set it\n+ # explicitly\n+ annotations = IAnnotations(self.context)\n+ annotations[self.ORDER_KEY] = value\n+\n def _pos(self, create=False):\n annotations = IAnnotations(self.context)\n if create:\ndiff --git a/src/plone/folder/partial.py b/src/plone/folder/partial.py\nindex 30beb2b..f1fe920 100644\n--- a/src/plone/folder/partial.py\n+++ b/src/plone/folder/partial.py\n@@ -27,9 +27,17 @@ def __init__(self, context):\n def order(self):\n context = aq_base(self.context)\n if not hasattr(context, ORDER_ATTR):\n- setattr(context, ORDER_ATTR, [])\n+ self.order = []\n return getattr(context, ORDER_ATTR)\n \n+ @order.setter\n+ def order(self, value):\n+ # We added a setter because in py2 order is modified inplace\n+ # with .sort() while in py3 we sort with sorted and thus need to set it\n+ # explicitly\n+ context = aq_base(self.context)\n+ setattr(context, ORDER_ATTR, value)\n+\n def notifyAdded(self, id):\n """ see interfaces.py """\n assert not id in self.order\n@@ -139,8 +147,7 @@ def keyfn(id):\n if callable(attr):\n return attr()\n return attr\n-\n- self.order.sort(None, keyfn, bool(reverse))\n+ self.order = sorted(self.order, key=keyfn, reverse=bool(reverse))\n self.context._p_changed = True # the order was changed\n return -1\n \ndiff --git a/src/plone/folder/tests/test_dict_interface.py b/src/plone/folder/tests/test_dict_interface.py\nindex 2035202..5917e88 100644\n--- a/src/plone/folder/tests/test_dict_interface.py\n+++ b/src/plone/folder/tests/test_dict_interface.py\n@@ -60,7 +60,7 @@ def test_to_verify_ticket_9120(self):\n del folder[\'ob2\']\n del folder[\'ob3\']\n self.assertEquals(folder.keys(), [\'ob1\', \'ob4\'])\n- self.assertEquals(map(aq_base, folder.values()), [ob1, ob4])\n+ self.assertEquals(list(map(aq_base, folder.values())), [ob1, ob4])\n self.assertEquals([key in folder for key in folder], [True, True])\n \n \ndiff --git a/src/plone/folder/tests/test_integration.py b/src/plone/folder/tests/test_integration.py\nindex c4be5bb..959c627 100644\n--- a/src/plone/folder/tests/test_integration.py\n+++ b/src/plone/folder/tests/test_integration.py\n@@ -3,7 +3,7 @@\n from plone.folder.interfaces import IOrderable\n from plone.folder.ordered import OrderedBTreeFolderBase\n from plone.folder.testing import PLONEFOLDER_FUNCTIONAL_TESTING\n-from six import StringIO\n+from six import BytesIO\n from transaction import savepoint\n from zope.interface import implementer\n \n@@ -28,7 +28,7 @@ def testExportDoesntIncludeParent(self):\n foo[\'bar\'] = DummyFolder(\'bar\')\n savepoint(optimistic=True) # savepoint assigns oids\n # now let\'s export to a buffer and check the objects...\n- exp = StringIO()\n+ exp = BytesIO()\n self.app._p_jar.exportFile(foo.bar._p_oid, exp)\n- self.assertTrue(\'bar\' in exp.getvalue())\n- self.assertFalse(\'foo\' in exp.getvalue())\n+ self.assertTrue(b\'bar\' in exp.getvalue())\n+ self.assertFalse(b\'foo\' in exp.getvalue())\n' +b"diff --git a/plone/autoform/base.py b/plone/autoform/base.py\nindex 7addd53..ba7a740 100644\n--- a/plone/autoform/base.py\n+++ b/plone/autoform/base.py\n@@ -259,4 +259,7 @@ def _process_field_moves(self, rules):\n self._process_field_moves(rule.get('with', {}))\n \n def _process_group_order(self):\n- self.groups.sort(key=attrgetter('order'))\n+ try:\n+ self.groups.sort(key=attrgetter('order'))\n+ except TypeError:\n+ pass\n" -Repository: plone.folder +Repository: plone.autoform Branch: refs/heads/master -Date: 2018-09-18T15:52:53+02:00 -Author: Jens W. Klein (jensens) -Commit: https://github.com/plone/plone.folder/commit/16e6913531ef29bd93c5f816a9768031bf8ffcd7 +Date: 2018-09-18T15:24:26+02:00 +Author: Philip Bauer (pbauer) +Commit: https://github.com/plone/plone.autoform/commit/64f4d7ddeff02f1a0fe29ddd65fa762f40d79b77 -fix deprecated lazymap import +fix check for exception-message for py3 Files changed: -M CHANGES.rst -M src/plone/folder/ordered.py +M plone/autoform/base.py -b"diff --git a/CHANGES.rst b/CHANGES.rst\nindex 017b3d7..e76805f 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -31,6 +31,9 @@ Bug fixes:\n - More Python 2 / 3 compatibility\n [pbauer, ale-rt]\n \n+- Fix deprecated LazyMap import\n+ [jensens]\n+\n \n 1.0.11 (2018-04-08)\n -------------------\ndiff --git a/src/plone/folder/ordered.py b/src/plone/folder/ordered.py\nindex 00d1046..32f3c42 100644\n--- a/src/plone/folder/ordered.py\n+++ b/src/plone/folder/ordered.py\n@@ -10,14 +10,15 @@\n from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base\n from Products.CMFCore.permissions import ModifyPortalContent\n from Products.CMFCore.PortalFolder import PortalFolderBase\n-from Products.ZCatalog.Lazy import LazyMap\n from zope.annotation.interfaces import IAttributeAnnotatable\n from zope.component import getAdapter\n from zope.component import queryAdapter\n from zope.interface import implementer\n+from ZTUtils.Lazy import LazyMap\n \n import pkg_resources\n \n+\n HAS_ZSERVER = True\n try:\n dist = pkg_resources.get_distribution('ZServer')\n" +b"diff --git a/plone/autoform/base.py b/plone/autoform/base.py\nindex ba7a740..0d3a337 100644\n--- a/plone/autoform/base.py\n+++ b/plone/autoform/base.py\n@@ -11,6 +11,7 @@\n from z3c.form.util import expandPrefix\n \n import logging\n+import six\n \n \n logger = logging.getLogger(__name__)\n@@ -242,9 +243,13 @@ def _process_field_moves(self, rules):\n try:\n move(self, name, before=before, after=after, prefix=prefix)\n except KeyError as e:\n+ if six.PY2:\n+ message = e.message\n+ else:\n+ message = e.args[0]\n if (\n- e.message.startswith('Field ') and\n- e.message.endswith(' not found')\n+ message.startswith('Field ') and\n+ message.endswith(' not found')\n ):\n # The relative_to field doesn't exist\n logger.warning(\n" -Repository: plone.folder +Repository: plone.autoform Branch: refs/heads/master -Date: 2018-09-18T15:52:53+02:00 -Author: David Glick (davisagli) -Commit: https://github.com/plone/plone.folder/commit/4a970c1bca724a5701eb9b2111bdf183a5928c15 +Date: 2018-09-18T15:24:26+02:00 +Author: Philip Bauer (pbauer) +Commit: https://github.com/plone/plone.autoform/commit/e5b403b518bc758c81622d123b54cb9b5f9ed67b -Stabilize order of unordered items in partial ordering +fix some tests for py3 Files changed: -M src/plone/folder/partial.py -M src/plone/folder/tests/test_partialordering.py +M plone/autoform/tests/test_supermodel_handler.py -b'diff --git a/src/plone/folder/partial.py b/src/plone/folder/partial.py\nindex f1fe920..6891dd2 100644\n--- a/src/plone/folder/partial.py\n+++ b/src/plone/folder/partial.py\n@@ -58,10 +58,12 @@ def notifyRemoved(self, id):\n def idsInOrder(self, onlyOrderables=False):\n """ see interfaces.py """\n ordered = list(self.order)\n+ ordered_set = set(ordered)\n if not onlyOrderables:\n ids = aq_base(self.context).objectIds(ordered=False)\n- unordered = set(ids).difference(set(ordered))\n- ordered += list(unordered)\n+ for id in ids:\n+ if id not in ordered_set:\n+ ordered.append(id)\n return ordered\n \n def moveObjectsByDelta(self, ids, delta, subset_ids=None,\ndiff --git a/src/plone/folder/tests/test_partialordering.py b/src/plone/folder/tests/test_partialordering.py\nindex bedb9f3..8468c63 100644\n--- a/src/plone/folder/tests/test_partialordering.py\n+++ b/src/plone/folder/tests/test_partialordering.py\n@@ -26,7 +26,7 @@ def create(self):\n container[\'c2\'] = Chaoticle(\'c2\', \'mt2\')\n container[\'c3\'] = Chaoticle(\'c3\', \'mt1\')\n container[\'o4\'] = Orderable(\'o4\', \'mt2\')\n- self.unordered = [\'c3\', \'c2\', \'c1\']\n+ self.unordered = [\'c1\', \'c2\', \'c3\']\n ordering = container.getOrdering()\n return container, ordering\n \n' +b'diff --git a/plone/autoform/tests/test_supermodel_handler.py b/plone/autoform/tests/test_supermodel_handler.py\nindex 0044458..c8bd266 100644\n--- a/plone/autoform/tests/test_supermodel_handler.py\n+++ b/plone/autoform/tests/test_supermodel_handler.py\n@@ -353,9 +353,9 @@ class IDummy(Interface):\n \n self.assertEqual(\n etree.tostring(fieldNode),\n- \'\'\n+ b\'\'\n )\n \n def test_write_parameterized_widget_default(self):\n@@ -372,7 +372,7 @@ class IDummy(Interface):\n \n self.assertEqual(\n etree.tostring(fieldNode),\n- \'\'\n+ b\'\'\n )\n \n def test_write_parameterized_widget_with_handler(self):\n@@ -389,12 +389,12 @@ class IDummy(Interface):\n \n self.assertEqual(\n etree.tostring(fieldNode),\n- \'\'\n- \'custom\'\n- \'\')\n+ b\'\'\n+ b\'custom\'\n+ b\'\')\n \n def test_write_parameterized_widget_default_with_handler(self):\n from plone.autoform.widgets import ParameterizedWidget\n@@ -410,9 +410,9 @@ class IDummy(Interface):\n \n self.assertEqual(\n etree.tostring(fieldNode),\n- \'\'\n- \'custom\')\n+ b\'\'\n+ b\'custom\')\n \n \n class TestSecuritySchema(unittest.TestCase):\n' -Repository: plone.folder +Repository: plone.autoform Branch: refs/heads/master -Date: 2018-09-18T15:52:53+02:00 -Author: hvelarde (hvelarde) -Commit: https://github.com/plone/plone.folder/commit/ba479fd381af26b207f9a111086fe1708ae95a80 +Date: 2018-09-18T15:24:26+02:00 +Author: Philip Bauer (pbauer) +Commit: https://github.com/plone/plone.autoform/commit/ee53b27627126e52e003b90f66803ee066f0a06f -Fix nonzero-method +fix doctests in py3 Files changed: -M src/plone/folder/ordered.py +M plone/autoform/autoform.rst +M plone/autoform/supermodel.txt +M plone/autoform/tests/subform.txt +M plone/autoform/tests/test_doctests.py +M plone/autoform/view.txt -b'diff --git a/src/plone/folder/ordered.py b/src/plone/folder/ordered.py\nindex 32f3c42..1fb24e0 100644\n--- a/src/plone/folder/ordered.py\n+++ b/src/plone/folder/ordered.py\n@@ -39,7 +39,7 @@ class OrderedBTreeFolderBase(BTreeFolder2Base):\n \n security = ClassSecurityInfo()\n \n- def __nonzero__(self):\n+ def __bool__(self):\n """ a folder is something, even if it\'s empty """\n return True\n \n' +b'diff --git a/plone/autoform/autoform.rst b/plone/autoform/autoform.rst\nindex 6251cf8..c57e1a8 100644\n--- a/plone/autoform/autoform.rst\n+++ b/plone/autoform/autoform.rst\n@@ -44,7 +44,8 @@ First, let\'s load this package\'s ZCML so that we can run the tests::\n ...\n ... \n ... """\n- >>> from StringIO import StringIO\n+ >>> import six\n+ >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n \n@@ -313,9 +314,9 @@ has taken the label and description from the first definition::\n >>> len(test_form.groups)\n 1\n >>> test_form.groups[0].label\n- u\'Fieldset one\'\n+ \'Fieldset one\'\n >>> test_form.groups[0].description\n- u\'Description of fieldset one\'\n+ \'Description of fieldset one\'\n >>> test_form.groups[0].fields.keys()\n [\'three\', \'IOtherSchema.three\']\n \ndiff --git a/plone/autoform/supermodel.txt b/plone/autoform/supermodel.txt\nindex 3fec8a0..d624af6 100644\n--- a/plone/autoform/supermodel.txt\n+++ b/plone/autoform/supermodel.txt\n@@ -19,7 +19,8 @@ First, let\'s load this package\'s ZCML so that we can run the tests:\n ...\n ... \n ... """\n- >>> from StringIO import StringIO\n+ >>> import six\n+ >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n \ndiff --git a/plone/autoform/tests/subform.txt b/plone/autoform/tests/subform.txt\nindex 6a681a4..a2d2864 100644\n--- a/plone/autoform/tests/subform.txt\n+++ b/plone/autoform/tests/subform.txt\n@@ -32,7 +32,8 @@ First, let\'s load this package\'s ZCML so that we can run the tests:\n ...\n ... \n ... """\n- >>> from StringIO import StringIO\n+ >>> import six\n+ >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n \ndiff --git a/plone/autoform/tests/test_doctests.py b/plone/autoform/tests/test_doctests.py\nindex 271d8a4..1f8262b 100644\n--- a/plone/autoform/tests/test_doctests.py\n+++ b/plone/autoform/tests/test_doctests.py\n@@ -4,6 +4,8 @@\n from plone.testing.zca import UNIT_TESTING\n \n import doctest\n+import re\n+import six\n import unittest\n \n \n@@ -15,12 +17,21 @@\n ]\n \n \n+class Py23DocChecker(doctest.OutputChecker):\n+ def check_output(self, want, got, optionflags):\n+ if six.PY2:\n+ got = re.sub("u\'(.*?)\'", "\'\\\\1\'", want)\n+ # want = re.sub("b\'(.*?)\'", "\'\\\\1\'", want)\n+ return doctest.OutputChecker.check_output(self, want, got, optionflags)\n+\n+\n def test_suite():\n tests = [\n layered(\n doctest.DocFileSuite(\n test_file,\n optionflags=optionflags,\n+ checker=Py23DocChecker(),\n ),\n layer=UNIT_TESTING,\n )\ndiff --git a/plone/autoform/view.txt b/plone/autoform/view.txt\nindex ea2c002..9264f5a 100644\n--- a/plone/autoform/view.txt\n+++ b/plone/autoform/view.txt\n@@ -15,7 +15,8 @@ First, let\'s load this package\'s ZCML so that we can run the tests:\n ...\n ... \n ... """\n- >>> from StringIO import StringIO\n+ >>> import six\n+ >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n \n@@ -79,7 +80,7 @@ set up.\n >>> context.summary = u"Summary"\n \n >>> view = TestView(context, request)\n- >>> print view()\n+ >>> print(view())\n
My title widget says\n Test title\n@@ -96,9 +97,10 @@ updated, we have access to widgets in the default fieldset:\n There is also a shortcut to allow access to any widget by (possibly prefixed)\n name:\n \n- >>> view.w.items()\n- [(\'body\', ),\n- (\'ISecondarySchema.summary\', ),\n+ >>> items = sorted(list(view.w.items()))\n+ >>> items\n+ [(\'ISecondarySchema.summary\', ),\n+ (\'body\', ),\n (\'title\', )]\n \n You can also see fieldsets (groups) either in order:\n@@ -108,7 +110,7 @@ You can also see fieldsets (groups) either in order:\n \n or looked up by name:\n \n- >>> view.fieldsets.items()\n+ >>> list(view.fieldsets.items())\n [(\'secondary\', )]\n \n Note how the schema name is used as a prefix to all additional schemata. If\n@@ -117,7 +119,8 @@ you wish to flatten the namespace, you can set ignorePrefix to true:\n >>> view = TestView(context, request)\n >>> view.ignorePrefix = True\n >>> view.update()\n- >>> view.w.items()\n+ >>> items = sorted(view.w.items())\n+ >>> items\n [(\'body\', ),\n (\'summary\', ),\n (\'title\', )]\n' -Repository: plone.folder +Repository: plone.autoform Branch: refs/heads/master -Date: 2018-09-18T15:55:50+02:00 +Date: 2018-09-18T15:24:26+02:00 Author: Philip Bauer (pbauer) -Commit: https://github.com/plone/plone.folder/commit/7a89ca034a766f8f148a522be4ad21369662b6b4 +Commit: https://github.com/plone/plone.autoform/commit/316b00e05904d607b04f4ab1b86669e8e4e8bf18 -add changenote and classifiers +fix test in py3 Files changed: -M CHANGES.rst -M setup.py +M plone/autoform/supermodel.txt -b'diff --git a/CHANGES.rst b/CHANGES.rst\nindex e76805f..aaa1ae9 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -15,7 +15,14 @@ New features:\n \n Bug fixes:\n \n-- *add item here*\n+- Stabilize order of unordered items in partial ordering.\n+ [davisagli]\n+\n+- Fix ordering of content in folder in python 3.\n+ [pbauer]\n+\n+- Fix tests in py3.\n+ [pbauer]\n \n \n 2.0.0 (2018-06-20)\ndiff --git a/setup.py b/setup.py\nindex c511d4a..7012306 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -13,6 +13,7 @@\n "Framework :: Plone",\n "Framework :: Plone :: 5.0",\n "Framework :: Plone :: 5.1",\n+ "Framework :: Plone :: 5.2",\n "Framework :: Zope2",\n "Intended Audience :: Developers",\n "Intended Audience :: System Administrators",\n@@ -21,6 +22,8 @@\n "Operating System :: OS Independent",\n "Programming Language :: Python",\n "Programming Language :: Python :: 2.7",\n+ "Programming Language :: Python :: 3.6",\n+ "Programming Language :: Python :: 3.7",\n ],\n keywords=\'folder btree order\',\n author=\'Plone Foundation\',\n' +b"diff --git a/plone/autoform/supermodel.txt b/plone/autoform/supermodel.txt\nindex d624af6..c823ed5 100644\n--- a/plone/autoform/supermodel.txt\n+++ b/plone/autoform/supermodel.txt\n@@ -97,7 +97,7 @@ The interface defined in the model should now have the relevant form data:\n [(, 'one', 'true'), (, 'three', 'true'), (, 'three', 'false')]\n >>> model.schema.getTaggedValue(ORDER_KEY)\n [('one', 'after', 'two'), ('three', 'before', 'two')]\n- >>> model.schema.getTaggedValue(MODES_KEY)\n+ >>> sorted(model.schema.getTaggedValue(MODES_KEY))\n [(, 'three', 'input'), (, 'two', 'hidden')]\n >>> model.schema.getTaggedValue(READ_PERMISSIONS_KEY)\n {'one': 'zope2.View'}\n" -Repository: plone.folder +Repository: plone.autoform Branch: refs/heads/master -Date: 2018-09-18T17:37:31+02:00 +Date: 2018-09-18T15:24:54+02:00 Author: Philip Bauer (pbauer) -Commit: https://github.com/plone/plone.folder/commit/014cf1cf13d476c02471ac1b89061408bd9ad973 +Commit: https://github.com/plone/plone.autoform/commit/2c45d4b079634be7965ae03914ccbd881e6449c7 -fix import for Zope2 +add changenote and classifiers Files changed: -M src/plone/folder/ordered.py +M CHANGES.rst +M setup.py -b'diff --git a/src/plone/folder/ordered.py b/src/plone/folder/ordered.py\nindex 1fb24e0..8b6e0d4 100644\n--- a/src/plone/folder/ordered.py\n+++ b/src/plone/folder/ordered.py\n@@ -14,7 +14,11 @@\n from zope.component import getAdapter\n from zope.component import queryAdapter\n from zope.interface import implementer\n-from ZTUtils.Lazy import LazyMap\n+try:\n+ from ZTUtils.Lazy import LazyMap\n+except ImportError:\n+ # bbb import for Zope2\n+ from Products.ZCatalog.Lazy import LazyMap\n \n import pkg_resources\n \n' +b'diff --git a/CHANGES.rst b/CHANGES.rst\nindex fdcb184..affefdc 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -10,7 +10,8 @@ Breaking changes:\n \n New features:\n \n-- *add item here*\n+- Add support for python 3\n+ [pbauer]\n \n Bug fixes:\n \ndiff --git a/setup.py b/setup.py\nindex fb248f6..4dd28bb 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -23,9 +23,12 @@ def read(*rnames):\n classifiers=[\n "Framework :: Plone",\n "Framework :: Plone :: 5.1",\n+ "Framework :: Plone :: 5.2",\n "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",\n "Programming Language :: Python",\n "Programming Language :: Python :: 2.7",\n+ "Programming Language :: Python :: 3.6",\n+ "Programming Language :: Python :: 3.7",\n "Topic :: Software Development :: Libraries :: Python Modules",\n ],\n keywords=\'plone form z3c.form\',\n' -Repository: plone.folder +Repository: plone.autoform Branch: refs/heads/master -Date: 2018-09-18T19:55:42+02:00 +Date: 2018-09-18T19:55:57+02:00 Author: Philip Bauer (pbauer) -Commit: https://github.com/plone/plone.folder/commit/bad4ad2409424e2662f8567561187005328f2b4a +Commit: https://github.com/plone/plone.autoform/commit/dec28e5cc89e0b006878c6fff3dbb62041f92ee0 -Merge pull request #9 from plone/python3 +Merge pull request #34 from plone/python3 -fix ordering of content in folder in py3 +fix sorting when group.order is None Files changed: M CHANGES.rst +M plone/autoform/autoform.rst +M plone/autoform/base.py +M plone/autoform/supermodel.txt +M plone/autoform/tests/subform.txt +M plone/autoform/tests/test_doctests.py +M plone/autoform/tests/test_supermodel_handler.py +M plone/autoform/view.txt M setup.py -M src/plone/folder/default.py -M src/plone/folder/ordered.py -M src/plone/folder/partial.py -M src/plone/folder/tests/test_dict_interface.py -M src/plone/folder/tests/test_integration.py -M src/plone/folder/tests/test_partialordering.py - -b'diff --git a/CHANGES.rst b/CHANGES.rst\nindex 017b3d7..aaa1ae9 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -15,7 +15,14 @@ New features:\n \n Bug fixes:\n \n-- *add item here*\n+- Stabilize order of unordered items in partial ordering.\n+ [davisagli]\n+\n+- Fix ordering of content in folder in python 3.\n+ [pbauer]\n+\n+- Fix tests in py3.\n+ [pbauer]\n \n \n 2.0.0 (2018-06-20)\n@@ -31,6 +38,9 @@ Bug fixes:\n - More Python 2 / 3 compatibility\n [pbauer, ale-rt]\n \n+- Fix deprecated LazyMap import\n+ [jensens]\n+\n \n 1.0.11 (2018-04-08)\n -------------------\ndiff --git a/setup.py b/setup.py\nindex c511d4a..7012306 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -13,6 +13,7 @@\n "Framework :: Plone",\n "Framework :: Plone :: 5.0",\n "Framework :: Plone :: 5.1",\n+ "Framework :: Plone :: 5.2",\n "Framework :: Zope2",\n "Intended Audience :: Developers",\n "Intended Audience :: System Administrators",\n@@ -21,6 +22,8 @@\n "Operating System :: OS Independent",\n "Programming Language :: Python",\n "Programming Language :: Python :: 2.7",\n+ "Programming Language :: Python :: 3.6",\n+ "Programming Language :: Python :: 3.7",\n ],\n keywords=\'folder btree order\',\n author=\'Plone Foundation\',\ndiff --git a/src/plone/folder/default.py b/src/plone/folder/default.py\nindex c7ad8d7..266498d 100644\n--- a/src/plone/folder/default.py\n+++ b/src/plone/folder/default.py\n@@ -136,8 +136,9 @@ def keyfn(obj_id):\n if callable(attr):\n return attr()\n return attr\n- order.sort(None, keyfn, bool(reverse))\n-\n+ # order.sort(cmd=None, key=keyfn, reverse=bool(reverse))\n+ order = sorted(order, key=keyfn, reverse=bool(reverse))\n+ self._set_order(order)\n for n, obj_id in enumerate(order):\n pos[obj_id] = n\n return -1\n@@ -165,6 +166,13 @@ def _order(self, create=False):\n return annotations.setdefault(self.ORDER_KEY, PersistentList())\n return annotations.get(self.ORDER_KEY, [])\n \n+ def _set_order(self, value):\n+ # We added a setter because in py2 _order is modified inplace\n+ # with .sort() while in py3 we sort with sorted and thus need to set it\n+ # explicitly\n+ annotations = IAnnotations(self.context)\n+ annotations[self.ORDER_KEY] = value\n+\n def _pos(self, create=False):\n annotations = IAnnotations(self.context)\n if create:\ndiff --git a/src/plone/folder/ordered.py b/src/plone/folder/ordered.py\nindex 00d1046..8b6e0d4 100644\n--- a/src/plone/folder/ordered.py\n+++ b/src/plone/folder/ordered.py\n@@ -10,14 +10,19 @@\n from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base\n from Products.CMFCore.permissions import ModifyPortalContent\n from Products.CMFCore.PortalFolder import PortalFolderBase\n-from Products.ZCatalog.Lazy import LazyMap\n from zope.annotation.interfaces import IAttributeAnnotatable\n from zope.component import getAdapter\n from zope.component import queryAdapter\n from zope.interface import implementer\n+try:\n+ from ZTUtils.Lazy import LazyMap\n+except ImportError:\n+ # bbb import for Zope2\n+ from Products.ZCatalog.Lazy import LazyMap\n \n import pkg_resources\n \n+\n HAS_ZSERVER = True\n try:\n dist = pkg_resources.get_distribution(\'ZServer\')\n@@ -38,7 +43,7 @@ class OrderedBTreeFolderBase(BTreeFolder2Base):\n \n security = ClassSecurityInfo()\n \n- def __nonzero__(self):\n+ def __bool__(self):\n """ a folder is something, even if it\'s empty """\n return True\n \ndiff --git a/src/plone/folder/partial.py b/src/plone/folder/partial.py\nindex 30beb2b..6891dd2 100644\n--- a/src/plone/folder/partial.py\n+++ b/src/plone/folder/partial.py\n@@ -27,9 +27,17 @@ def __init__(self, context):\n def order(self):\n context = aq_base(self.context)\n if not hasattr(context, ORDER_ATTR):\n- setattr(context, ORDER_ATTR, [])\n+ self.order = []\n return getattr(context, ORDER_ATTR)\n \n+ @order.setter\n+ def order(self, value):\n+ # We added a setter because in py2 order is modified inplace\n+ # with .sort() while in py3 we sort with sorted and thus need to set it\n+ # explicitly\n+ context = aq_base(self.context)\n+ setattr(context, ORDER_ATTR, value)\n+\n def notifyAdded(self, id):\n """ see interfaces.py """\n assert not id in self.order\n@@ -50,10 +58,12 @@ def notifyRemoved(self, id):\n def idsInOrder(self, onlyOrderables=False):\n """ see interfaces.py """\n ordered = list(self.order)\n+ ordered_set = set(ordered)\n if not onlyOrderables:\n ids = aq_base(self.context).objectIds(ordered=False)\n- unordered = set(ids).difference(set(ordered))\n- ordered += list(unordered)\n+ for id in ids:\n+ if id not in ordered_set:\n+ ordered.append(id)\n return ordered\n \n def moveObjectsByDelta(self, ids, delta, subset_ids=None,\n@@ -139,8 +149,7 @@ def keyfn(id):\n if callable(attr):\n return attr()\n return attr\n-\n- self.order.sort(None, keyfn, bool(reverse))\n+ self.order = sorted(self.order, key=keyfn, reverse=bool(reverse))\n self.context._p_changed = True # the order was changed\n return -1\n \ndiff --git a/src/plone/folder/tests/test_dict_interface.py b/src/plone/folder/tests/test_dict_interface.py\nindex 2035202..5917e88 100644\n--- a/src/plone/folder/tests/test_dict_interface.py\n+++ b/src/plone/folder/tests/test_dict_interface.py\n@@ -60,7 +60,7 @@ def test_to_verify_ticket_9120(self):\n del folder[\'ob2\']\n del folder[\'ob3\']\n self.assertEquals(folder.keys(), [\'ob1\', \'ob4\'])\n- self.assertEquals(map(aq_base, folder.values()), [ob1, ob4])\n+ self.assertEquals(list(map(aq_base, folder.values())), [ob1, ob4])\n self.assertEquals([key in folder for key in folder], [True, True])\n \n \ndiff --git a/src/plone/folder/tests/test_integration.py b/src/plone/folder/tests/test_integration.py\nindex c4be5bb..959c627 100644\n--- a/src/plone/folder/tests/test_integration.py\n+++ b/src/plone/folder/tests/test_integration.py\n@@ -3,7 +3,7 @@\n from plone.folder.interfaces import IOrderable\n from plone.folder.ordered import OrderedBTreeFolderBase\n from plone.folder.testing import PLONEFOLDER_FUNCTIONAL_TESTING\n-from six import StringIO\n+from six import BytesIO\n from transaction import savepoint\n from zope.interface import implementer\n \n@@ -28,7 +28,7 @@ def testExportDoesntIncludeParent(self):\n foo[\'bar\'] = DummyFolder(\'bar\')\n savepoint(optimistic=True) # savepoint assigns oids\n # now let\'s export to a buffer and check the objects...\n- exp = StringIO()\n+ exp = BytesIO()\n self.app._p_jar.exportFile(foo.bar._p_oid, exp)\n- self.assertTrue(\'bar\' in exp.getvalue())\n- self.assertFalse(\'foo\' in exp.getvalue())\n+ self.assertTrue(b\'bar\' in exp.getvalue())\n+ self.assertFalse(b\'foo\' in exp.getvalue())\ndiff --git a/src/plone/folder/tests/test_partialordering.py b/src/plone/folder/tests/test_partialordering.py\nindex bedb9f3..8468c63 100644\n--- a/src/plone/folder/tests/test_partialordering.py\n+++ b/src/plone/folder/tests/test_partialordering.py\n@@ -26,7 +26,7 @@ def create(self):\n container[\'c2\'] = Chaoticle(\'c2\', \'mt2\')\n container[\'c3\'] = Chaoticle(\'c3\', \'mt1\')\n container[\'o4\'] = Orderable(\'o4\', \'mt2\')\n- self.unordered = [\'c3\', \'c2\', \'c1\']\n+ self.unordered = [\'c1\', \'c2\', \'c3\']\n ordering = container.getOrdering()\n return container, ordering\n \n' + +b'diff --git a/CHANGES.rst b/CHANGES.rst\nindex fdcb184..affefdc 100644\n--- a/CHANGES.rst\n+++ b/CHANGES.rst\n@@ -10,7 +10,8 @@ Breaking changes:\n \n New features:\n \n-- *add item here*\n+- Add support for python 3\n+ [pbauer]\n \n Bug fixes:\n \ndiff --git a/plone/autoform/autoform.rst b/plone/autoform/autoform.rst\nindex 6251cf8..c57e1a8 100644\n--- a/plone/autoform/autoform.rst\n+++ b/plone/autoform/autoform.rst\n@@ -44,7 +44,8 @@ First, let\'s load this package\'s ZCML so that we can run the tests::\n ...\n ... \n ... """\n- >>> from StringIO import StringIO\n+ >>> import six\n+ >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n \n@@ -313,9 +314,9 @@ has taken the label and description from the first definition::\n >>> len(test_form.groups)\n 1\n >>> test_form.groups[0].label\n- u\'Fieldset one\'\n+ \'Fieldset one\'\n >>> test_form.groups[0].description\n- u\'Description of fieldset one\'\n+ \'Description of fieldset one\'\n >>> test_form.groups[0].fields.keys()\n [\'three\', \'IOtherSchema.three\']\n \ndiff --git a/plone/autoform/base.py b/plone/autoform/base.py\nindex 7addd53..0d3a337 100644\n--- a/plone/autoform/base.py\n+++ b/plone/autoform/base.py\n@@ -11,6 +11,7 @@\n from z3c.form.util import expandPrefix\n \n import logging\n+import six\n \n \n logger = logging.getLogger(__name__)\n@@ -242,9 +243,13 @@ def _process_field_moves(self, rules):\n try:\n move(self, name, before=before, after=after, prefix=prefix)\n except KeyError as e:\n+ if six.PY2:\n+ message = e.message\n+ else:\n+ message = e.args[0]\n if (\n- e.message.startswith(\'Field \') and\n- e.message.endswith(\' not found\')\n+ message.startswith(\'Field \') and\n+ message.endswith(\' not found\')\n ):\n # The relative_to field doesn\'t exist\n logger.warning(\n@@ -259,4 +264,7 @@ def _process_field_moves(self, rules):\n self._process_field_moves(rule.get(\'with\', {}))\n \n def _process_group_order(self):\n- self.groups.sort(key=attrgetter(\'order\'))\n+ try:\n+ self.groups.sort(key=attrgetter(\'order\'))\n+ except TypeError:\n+ pass\ndiff --git a/plone/autoform/supermodel.txt b/plone/autoform/supermodel.txt\nindex 3fec8a0..c823ed5 100644\n--- a/plone/autoform/supermodel.txt\n+++ b/plone/autoform/supermodel.txt\n@@ -19,7 +19,8 @@ First, let\'s load this package\'s ZCML so that we can run the tests:\n ...\n ... \n ... """\n- >>> from StringIO import StringIO\n+ >>> import six\n+ >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n \n@@ -96,7 +97,7 @@ The interface defined in the model should now have the relevant form data:\n [(, \'one\', \'true\'), (, \'three\', \'true\'), (, \'three\', \'false\')]\n >>> model.schema.getTaggedValue(ORDER_KEY)\n [(\'one\', \'after\', \'two\'), (\'three\', \'before\', \'two\')]\n- >>> model.schema.getTaggedValue(MODES_KEY)\n+ >>> sorted(model.schema.getTaggedValue(MODES_KEY))\n [(, \'three\', \'input\'), (, \'two\', \'hidden\')]\n >>> model.schema.getTaggedValue(READ_PERMISSIONS_KEY)\n {\'one\': \'zope2.View\'}\ndiff --git a/plone/autoform/tests/subform.txt b/plone/autoform/tests/subform.txt\nindex 6a681a4..a2d2864 100644\n--- a/plone/autoform/tests/subform.txt\n+++ b/plone/autoform/tests/subform.txt\n@@ -32,7 +32,8 @@ First, let\'s load this package\'s ZCML so that we can run the tests:\n ...\n ... \n ... """\n- >>> from StringIO import StringIO\n+ >>> import six\n+ >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n \ndiff --git a/plone/autoform/tests/test_doctests.py b/plone/autoform/tests/test_doctests.py\nindex 271d8a4..1f8262b 100644\n--- a/plone/autoform/tests/test_doctests.py\n+++ b/plone/autoform/tests/test_doctests.py\n@@ -4,6 +4,8 @@\n from plone.testing.zca import UNIT_TESTING\n \n import doctest\n+import re\n+import six\n import unittest\n \n \n@@ -15,12 +17,21 @@\n ]\n \n \n+class Py23DocChecker(doctest.OutputChecker):\n+ def check_output(self, want, got, optionflags):\n+ if six.PY2:\n+ got = re.sub("u\'(.*?)\'", "\'\\\\1\'", want)\n+ # want = re.sub("b\'(.*?)\'", "\'\\\\1\'", want)\n+ return doctest.OutputChecker.check_output(self, want, got, optionflags)\n+\n+\n def test_suite():\n tests = [\n layered(\n doctest.DocFileSuite(\n test_file,\n optionflags=optionflags,\n+ checker=Py23DocChecker(),\n ),\n layer=UNIT_TESTING,\n )\ndiff --git a/plone/autoform/tests/test_supermodel_handler.py b/plone/autoform/tests/test_supermodel_handler.py\nindex 0044458..c8bd266 100644\n--- a/plone/autoform/tests/test_supermodel_handler.py\n+++ b/plone/autoform/tests/test_supermodel_handler.py\n@@ -353,9 +353,9 @@ class IDummy(Interface):\n \n self.assertEqual(\n etree.tostring(fieldNode),\n- \'\'\n+ b\'\'\n )\n \n def test_write_parameterized_widget_default(self):\n@@ -372,7 +372,7 @@ class IDummy(Interface):\n \n self.assertEqual(\n etree.tostring(fieldNode),\n- \'\'\n+ b\'\'\n )\n \n def test_write_parameterized_widget_with_handler(self):\n@@ -389,12 +389,12 @@ class IDummy(Interface):\n \n self.assertEqual(\n etree.tostring(fieldNode),\n- \'\'\n- \'custom\'\n- \'\')\n+ b\'\'\n+ b\'custom\'\n+ b\'\')\n \n def test_write_parameterized_widget_default_with_handler(self):\n from plone.autoform.widgets import ParameterizedWidget\n@@ -410,9 +410,9 @@ class IDummy(Interface):\n \n self.assertEqual(\n etree.tostring(fieldNode),\n- \'\'\n- \'custom\')\n+ b\'\'\n+ b\'custom\')\n \n \n class TestSecuritySchema(unittest.TestCase):\ndiff --git a/plone/autoform/view.txt b/plone/autoform/view.txt\nindex ea2c002..9264f5a 100644\n--- a/plone/autoform/view.txt\n+++ b/plone/autoform/view.txt\n@@ -15,7 +15,8 @@ First, let\'s load this package\'s ZCML so that we can run the tests:\n ...\n ... \n ... """\n- >>> from StringIO import StringIO\n+ >>> import six\n+ >>> from six import StringIO\n >>> from zope.configuration import xmlconfig\n >>> xmlconfig.xmlconfig(StringIO(configuration))\n \n@@ -79,7 +80,7 @@ set up.\n >>> context.summary = u"Summary"\n \n >>> view = TestView(context, request)\n- >>> print view()\n+ >>> print(view())\n
My title widget says\n Test title\n@@ -96,9 +97,10 @@ updated, we have access to widgets in the default fieldset:\n There is also a shortcut to allow access to any widget by (possibly prefixed)\n name:\n \n- >>> view.w.items()\n- [(\'body\', ),\n- (\'ISecondarySchema.summary\', ),\n+ >>> items = sorted(list(view.w.items()))\n+ >>> items\n+ [(\'ISecondarySchema.summary\', ),\n+ (\'body\', ),\n (\'title\', )]\n \n You can also see fieldsets (groups) either in order:\n@@ -108,7 +110,7 @@ You can also see fieldsets (groups) either in order:\n \n or looked up by name:\n \n- >>> view.fieldsets.items()\n+ >>> list(view.fieldsets.items())\n [(\'secondary\', )]\n \n Note how the schema name is used as a prefix to all additional schemata. If\n@@ -117,7 +119,8 @@ you wish to flatten the namespace, you can set ignorePrefix to true:\n >>> view = TestView(context, request)\n >>> view.ignorePrefix = True\n >>> view.update()\n- >>> view.w.items()\n+ >>> items = sorted(view.w.items())\n+ >>> items\n [(\'body\', ),\n (\'summary\', ),\n (\'title\', )]\ndiff --git a/setup.py b/setup.py\nindex fb248f6..4dd28bb 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -23,9 +23,12 @@ def read(*rnames):\n classifiers=[\n "Framework :: Plone",\n "Framework :: Plone :: 5.1",\n+ "Framework :: Plone :: 5.2",\n "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",\n "Programming Language :: Python",\n "Programming Language :: Python :: 2.7",\n+ "Programming Language :: Python :: 3.6",\n+ "Programming Language :: Python :: 3.7",\n "Topic :: Software Development :: Libraries :: Python Modules",\n ],\n keywords=\'plone form z3c.form\',\n'