diff --git a/last_commit.txt b/last_commit.txt index 818ba06e33..d7d5cfe5d7 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,36 +1,108 @@ -Repository: plone.app.dexterity +Repository: plone.app.z3cform Branch: refs/heads/master -Date: 2024-09-14T21:24:58+02:00 -Author: 1letter (1letter) <1letter@gmx.de> -Commit: https://github.com/plone/plone.app.dexterity/commit/9074dd5adff36200638a4caf2a7a045fbf075365 +Date: 2024-09-16T14:34:45+02:00 +Author: Peter Mathis (petschki) +Commit: https://github.com/plone/plone.app.z3cform/commit/a783df5da2e68e78ad083437b3311d56906affab -Configuring with plone/meta +Implement `pat-contentbrowser` widget Files changed: -A news/+meta.internal -M .meta.toml -M pyproject.toml +A plone/app/z3cform/templates/contentbrowser_display.pt +A plone/app/z3cform/widgets/contentbrowser.py +M plone/app/z3cform/converters.py +M plone/app/z3cform/converters.zcml +M plone/app/z3cform/interfaces.py +M plone/app/z3cform/templates/relateditems_display.pt +M plone/app/z3cform/widgets.zcml -b'diff --git a/.meta.toml b/.meta.toml\nindex c2aed684..3e42afd1 100644\n--- a/.meta.toml\n+++ b/.meta.toml\n@@ -3,7 +3,7 @@\n # See the inline comments on how to expand/tweak this configuration file\n [meta]\n template = "default"\n-commit-id = "a89af8f2"\n+commit-id = "5d22fbf8"\n \n [pyproject]\n codespell_ignores = "hove"\ndiff --git a/news/+meta.internal b/news/+meta.internal\nnew file mode 100644\nindex 00000000..c08f5399\n--- /dev/null\n+++ b/news/+meta.internal\n@@ -0,0 +1,2 @@\n+Update configuration files.\n+[plone devs]\ndiff --git a/pyproject.toml b/pyproject.toml\nindex a6df4ae8..e229484f 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -119,6 +119,7 @@ Zope = [\n \'Products.CMFCore\', \'Products.CMFDynamicViewFTI\',\n ]\n python-dateutil = [\'dateutil\']\n+pytest-plone = [\'pytest\', \'zope.pytestlayer\', \'plone.testing\', \'plone.app.testing\']\n ignore-packages = [\'plone.app.content\', \'plone.app.relationfield\', \'plone.directives.form\', \'plone.directives.dexterity\', \'five.grok\', \'plone.app.intid\', \'plone.contentrules\', \'plone.schema\', \'z3c.relationfield\']\n \n ##\n' +b'diff --git a/plone/app/z3cform/converters.py b/plone/app/z3cform/converters.py\nindex 1e824628..1a8efedc 100644\n--- a/plone/app/z3cform/converters.py\n+++ b/plone/app/z3cform/converters.py\n@@ -3,11 +3,11 @@\n from datetime import time\n from plone.app.z3cform import utils\n from plone.app.z3cform.interfaces import IAjaxSelectWidget\n+from plone.app.z3cform.interfaces import IContentBrowserWidget\n from plone.app.z3cform.interfaces import IDatetimeWidget\n from plone.app.z3cform.interfaces import IDateWidget\n from plone.app.z3cform.interfaces import ILinkWidget\n from plone.app.z3cform.interfaces import IQueryStringWidget\n-from plone.app.z3cform.interfaces import IRelatedItemsWidget\n from plone.app.z3cform.interfaces import ISelectWidget\n from plone.app.z3cform.interfaces import ISingleCheckBoxBoolWidget\n from plone.app.z3cform.interfaces import ITimeWidget\n@@ -304,9 +304,9 @@ def toFieldValue(self, value):\n return collectionType(untokenized_value)\n \n \n-@adapter(IRelation, IRelatedItemsWidget)\n-class RelationChoiceRelatedItemsWidgetConverter(BaseDataConverter):\n- """Data converter for RelationChoice fields using the RelatedItemsWidget."""\n+@adapter(IRelation, IContentBrowserWidget)\n+class RelationChoiceContentBrowserWidgetConverter(BaseDataConverter):\n+ """Data converter for RelationChoice fields using the ContentBrowserWidget."""\n \n def toWidgetValue(self, value):\n if not value:\n@@ -328,8 +328,15 @@ def toFieldValue(self, value):\n return self.field.missing_value\n \n \n+# BBB\n+class RelationChoiceRelatedItemsWidgetConverter(\n+ RelationChoiceContentBrowserWidgetConverter\n+):\n+ """backwards compatibility"""\n+\n+\n @adapter(IRelation, ISequenceWidget)\n-class RelationChoiceSelectWidgetConverter(RelationChoiceRelatedItemsWidgetConverter):\n+class RelationChoiceSelectWidgetConverter(RelationChoiceContentBrowserWidgetConverter):\n """Data converter for RelationChoice fields using with SequenceWidgets,\n which expect sequence values.\n """\n@@ -341,9 +348,9 @@ def toWidgetValue(self, value):\n return [IUUID(value)]\n \n \n-@adapter(ICollection, IRelatedItemsWidget)\n-class RelatedItemsDataConverter(BaseDataConverter):\n- """Data converter for ICollection fields using the RelatedItemsWidget."""\n+@adapter(ICollection, IContentBrowserWidget)\n+class ContentBrowserDataConverter(BaseDataConverter):\n+ """Data converter for ICollection fields using the ContentBrowserWidget."""\n \n def toWidgetValue(self, value):\n """Converts from field value to widget.\n@@ -405,8 +412,13 @@ def toFieldValue(self, value):\n return collectionType(valueType(v) for v in value)\n \n \n+# BBB\n+class RelatedItemsDataConverter(ContentBrowserDataConverter):\n+ """backwards compatibility"""\n+\n+\n @adapter(IRelationList, ISequenceWidget)\n-class RelationListSelectWidgetDataConverter(RelatedItemsDataConverter):\n+class RelationListSelectWidgetDataConverter(ContentBrowserDataConverter):\n """Data converter for RelationChoice fields using with SequenceWidgets,\n which expect sequence values.\n """\ndiff --git a/plone/app/z3cform/converters.zcml b/plone/app/z3cform/converters.zcml\nindex 5aca1fc2..eabfba73 100644\n--- a/plone/app/z3cform/converters.zcml\n+++ b/plone/app/z3cform/converters.zcml\n@@ -8,16 +8,16 @@\n \n \n \n- \n+ \n \n \n- \n+ \n \ndiff --git a/plone/app/z3cform/interfaces.py b/plone/app/z3cform/interfaces.py\nindex 0c22c77a..9b4b065c 100644\n--- a/plone/app/z3cform/interfaces.py\n+++ b/plone/app/z3cform/interfaces.py\n@@ -91,6 +91,10 @@ class IRelatedItemsWidget(ITextWidget):\n """Marker interface for the RelatedItemsWidget."""\n \n \n+class IContentBrowserWidget(ITextWidget):\n+ """Marker interface for the RelatedItemsWidget."""\n+\n+\n class IRichTextWidget(patextfield_IRichTextWidget):\n """Marker interface for the TinyMCEWidget."""\n \ndiff --git a/plone/app/z3cform/templates/contentbrowser_display.pt b/plone/app/z3cform/templates/contentbrowser_display.pt\nnew file mode 100644\nindex 00000000..972a9702\n--- /dev/null\n+++ b/plone/app/z3cform/templates/contentbrowser_display.pt\n@@ -0,0 +1,44 @@\n+
\n+ \n+
\ndiff --git a/plone/app/z3cform/templates/relateditems_display.pt b/plone/app/z3cform/templates/relateditems_display.pt\nindex b6203c2b..a3b19ea7 100644\n--- a/plone/app/z3cform/templates/relateditems_display.pt\n+++ b/plone/app/z3cform/templates/relateditems_display.pt\n@@ -1,46 +1,44 @@\n-\n-
\n-
\n-
\n+ \n+ \n+ \n+ \n+ \n+\ndiff --git a/plone/app/z3cform/widgets.zcml b/plone/app/z3cform/widgets.zcml\nindex 5fd76ff2..fe65f2ca 100644\n--- a/plone/app/z3cform/widgets.zcml\n+++ b/plone/app/z3cform/widgets.zcml\n@@ -223,27 +223,36 @@\n \n \n \n+ \n+\n+ \n+\n \n \n \n \n \n \n -Commit: https://github.com/plone/plone.app.dexterity/commit/01777b09c67b1a11f55c996263cd9a01d58dfcf6 +Date: 2024-09-16T14:34:45+02:00 +Author: Peter Mathis (petschki) +Commit: https://github.com/plone/plone.app.z3cform/commit/576d427408cba8c24248b2994e21113e1654586b -Merge pull request #392 from plone/config-with-default-template-5d22fbf8 +changenote -Configuring with plone/meta +Files changed: +A news/197.feature + +b'diff --git a/news/197.feature b/news/197.feature\nnew file mode 100644\nindex 00000000..abc8225c\n--- /dev/null\n+++ b/news/197.feature\n@@ -0,0 +1,9 @@\n+Implement new `ContentBrowserWidget` for `pat-contentbrowser` pattern.\n+\n+The deprecated `RelatedItemsWidget` and `pat-relateditems` pattern is still available\n+and imports should not break. But the default widget and converter adapter registration for\n+z3c.relationfield is changed to the new widget.\n+\n+Since `plone.app.relationfield` defines the widget with `plone.autoform` schema\n+hints nothing changes until the package is updated to the new widget.\n+[petschki]\n' + +Repository: plone.app.z3cform + + +Branch: refs/heads/master +Date: 2024-09-16T14:34:45+02:00 +Author: Peter Mathis (petschki) +Commit: https://github.com/plone/plone.app.z3cform/commit/3e03ac9a0c3a91a390e7592217a4a60dddf1129e + +update tests + +Files changed: +M plone/app/z3cform/tests/test_widgets.py + +b'diff --git a/plone/app/z3cform/tests/test_widgets.py b/plone/app/z3cform/tests/test_widgets.py\nindex 77438a0b..f453ba14 100644\n--- a/plone/app/z3cform/tests/test_widgets.py\n+++ b/plone/app/z3cform/tests/test_widgets.py\n@@ -8,8 +8,8 @@\n from plone.app.testing import TEST_USER_ID\n from plone.app.z3cform.tests.layer import PAZ3CForm_INTEGRATION_TESTING\n from plone.app.z3cform.widgets.base import PatternFormElement\n+from plone.app.z3cform.widgets.contentbrowser import ContentBrowserWidget\n from plone.app.z3cform.widgets.datetime import DateWidget\n-from plone.app.z3cform.widgets.relateditems import RelatedItemsWidget\n from plone.app.z3cform.widgets.text import TextFieldWidget\n from plone.autoform.directives import widget\n from plone.autoform.form import AutoExtensibleForm\n@@ -1407,7 +1407,7 @@ class IRelationsType(Interface):\n multiple = RelationList(title="Multiple (Relations field)", required=False)\n \n \n-class RelatedItemsWidgetTemplateIntegrationTests(unittest.TestCase):\n+class ContentBrowserWidgetTemplateIntegrationTests(unittest.TestCase):\n layer = PAZ3CForm_INTEGRATION_TESTING\n \n def setUp(self):\n@@ -1444,7 +1444,7 @@ def test_related_items_widget_display_template(self):\n default_view.update()\n \n single = default_view.w["single"]\n- self.assertIsInstance(single, RelatedItemsWidget)\n+ self.assertIsInstance(single, ContentBrowserWidget)\n self.assertTrue(single.value, target.UID())\n items = single.items()\n self.assertIsInstance(items, ContentListing)\n@@ -1463,7 +1463,7 @@ def test_related_items_widget_display_template(self):\n )\n \n multiple = default_view.w["multiple"]\n- self.assertIsInstance(multiple, RelatedItemsWidget)\n+ self.assertIsInstance(multiple, ContentBrowserWidget)\n self.assertTrue(multiple.value, ";".join([target.UID(), doc.UID()]))\n items = multiple.items()\n self.assertIsInstance(items, ContentListing)\n@@ -1649,6 +1649,168 @@ def test_fieldwidget(self):\n self.assertIs(widget.request, request)\n \n \n+class ContentBrowserWidgetTests(unittest.TestCase):\n+ layer = PAZ3CForm_INTEGRATION_TESTING\n+\n+ def setUp(self):\n+ self.portal = self.layer["portal"]\n+ self.request = self.layer["request"]\n+ setRoles(self.portal, TEST_USER_ID, ["Manager"])\n+\n+ def test_single_selection(self):\n+ """The pattern_options value for maximumSelectionSize should\n+ be 1 when the field only allows a single selection."""\n+ from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget\n+\n+ field = Choice(\n+ __name__="selectfield",\n+ values=["one", "two", "three"],\n+ )\n+ widget = ContentBrowserFieldWidget(field, self.request)\n+ widget.context = self.portal\n+ widget.update()\n+ pattern_options = widget.get_pattern_options()\n+ self.assertEqual(pattern_options.get("maximumSelectionSize", 0), 1)\n+\n+ def test_multiple_selection(self):\n+ """The pattern_options key maximumSelectionSize shouldn\'t be\n+ set when the field allows multiple selections"""\n+ from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget\n+ from Zope2.App.schema import Zope2VocabularyRegistry\n+ from zope.schema.interfaces import ISource\n+\n+ field = List(\n+ __name__="selectfield",\n+ value_type=Choice(vocabulary="foobar"),\n+ )\n+ widget = ContentBrowserFieldWidget(field, self.request)\n+ widget.context = self.portal\n+\n+ vocab = Mock()\n+ alsoProvides(vocab, ISource)\n+ with mock.patch.object(Zope2VocabularyRegistry, "get", return_value=vocab):\n+ widget.update()\n+ patterns_options = widget.get_pattern_options()\n+ self.assertFalse("maximumSelectionSize" in patterns_options)\n+ self.assertEqual(\n+ patterns_options["vocabularyUrl"],\n+ "http://nohost/plone/@@getVocabulary?name=foobar&field=selectfield",\n+ )\n+\n+ def test_converter_RelationChoice(self):\n+ from plone.app.z3cform.converters import (\n+ RelationChoiceContentBrowserWidgetConverter,\n+ )\n+\n+ brain = Mock(getObject=Mock(return_value="obj"))\n+ portal_catalog = Mock(return_value=[brain])\n+ widget = Mock()\n+ converter = RelationChoiceContentBrowserWidgetConverter(\n+ TextLine(),\n+ widget,\n+ )\n+\n+ with mock.patch(\n+ "plone.app.z3cform.converters.IUUID",\n+ return_value="id",\n+ ):\n+ self.assertEqual(converter.toWidgetValue("obj"), "id")\n+ self.assertEqual(converter.toWidgetValue(None), None)\n+\n+ with mock.patch(\n+ "plone.app.z3cform.converters.getToolByName",\n+ return_value=portal_catalog,\n+ ):\n+ self.assertEqual(converter.toFieldValue("id"), "obj")\n+ self.assertEqual(converter.toFieldValue(None), None)\n+\n+ def test_converter_RelationList(self):\n+ from plone.app.z3cform.converters import ContentBrowserDataConverter\n+ from z3c.relationfield.interfaces import IRelationList\n+\n+ field = List()\n+ alsoProvides(field, IRelationList)\n+ brain1 = Mock(getObject=Mock(return_value="obj1"), UID="id1")\n+ brain2 = Mock(getObject=Mock(return_value="obj2"), UID="id2")\n+ portal_catalog = Mock(return_value=[brain1, brain2])\n+ widget = Mock(separator=";")\n+ converter = ContentBrowserDataConverter(field, widget)\n+\n+ self.assertEqual(converter.toWidgetValue(None), None)\n+ with mock.patch(\n+ "plone.app.z3cform.converters.IUUID",\n+ side_effect=["id1", "id2"],\n+ ):\n+ self.assertEqual(\n+ converter.toWidgetValue(["obj1", "obj2"]),\n+ "id1;id2",\n+ )\n+\n+ self.assertEqual(converter.toFieldValue(None), None)\n+ with mock.patch(\n+ "plone.app.z3cform.converters.getToolByName",\n+ return_value=portal_catalog,\n+ ):\n+ self.assertEqual(\n+ converter.toFieldValue("id1;id2"),\n+ ["obj1", "obj2"],\n+ )\n+\n+ def test_converter_List_of_Choice(self):\n+ from plone.app.z3cform.converters import ContentBrowserDataConverter\n+\n+ fields = (\n+ List(),\n+ List(value_type=TextLine()),\n+ List(value_type=BytesLine()),\n+ List(value_type=Choice(values=["one", "two", "three"])),\n+ )\n+ for field in fields:\n+ expected_value_type = getattr(\n+ field.value_type,\n+ "_type",\n+ str,\n+ )\n+ if expected_value_type is None:\n+ expected_value_type = str\n+ widget = Mock(separator=";")\n+ converter = ContentBrowserDataConverter(field, widget)\n+\n+ self.assertEqual(converter.toWidgetValue(None), None)\n+ self.assertEqual(\n+ converter.toWidgetValue(["id1", "id2"]),\n+ "id1;id2",\n+ )\n+\n+ self.assertEqual(converter.toFieldValue(None), None)\n+ if expected_value_type == bytes:\n+ expected = [b"id1", b"id2"]\n+ else:\n+ expected = ["id1", "id2"]\n+ self.assertEqual(\n+ converter.toFieldValue("id1;id2"),\n+ expected,\n+ )\n+\n+ self.assertEqual(converter.toFieldValue(None), None)\n+ self.assertEqual(\n+ type(converter.toFieldValue("id1;id2")[0]),\n+ expected_value_type,\n+ )\n+\n+ def test_fieldwidget(self):\n+ from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget\n+ from plone.app.z3cform.widgets.contentbrowser import ContentBrowserWidget\n+\n+ field = Mock(__name__="field", title="", required=True)\n+ vocabulary = Mock()\n+ request = Mock()\n+ widget = ContentBrowserFieldWidget(field, vocabulary, request)\n+ self.assertTrue(isinstance(widget, ContentBrowserWidget))\n+ self.assertIs(widget.field, field)\n+ self.assertIs(widget.request, request)\n+\n+\n class RichTextWidgetTests(unittest.TestCase):\n layer = PAZ3CForm_INTEGRATION_TESTING\n \n' + +Repository: plone.app.z3cform + + +Branch: refs/heads/master +Date: 2024-09-16T14:34:45+02:00 +Author: Peter Mathis (petschki) +Commit: https://github.com/plone/plone.app.z3cform/commit/54ce09d3791396fa089c0e200eb1d0ce57bb0de7 + +switch `LinkWidget` to contentbrowser + +Files changed: +M plone/app/z3cform/templates/link_input.pt + +b'diff --git a/plone/app/z3cform/templates/link_input.pt b/plone/app/z3cform/templates/link_input.pt\nindex ff3e0f39..853760f0 100644\n--- a/plone/app/z3cform/templates/link_input.pt\n+++ b/plone/app/z3cform/templates/link_input.pt\n@@ -25,11 +25,11 @@\n
\n
\n \n- \n
\n
\n' + +Repository: plone.app.z3cform + + +Branch: refs/heads/master +Date: 2024-09-16T14:59:42+02:00 +Author: Peter Mathis (petschki) +Commit: https://github.com/plone/plone.app.z3cform/commit/bc626b59d07421997c1dd968cdefd080eaa70e2d + +Add BBB adapters + +Files changed: +M plone/app/z3cform/converters.py +M plone/app/z3cform/converters.zcml + +b'diff --git a/plone/app/z3cform/converters.py b/plone/app/z3cform/converters.py\nindex 1a8efedc..fb586c65 100644\n--- a/plone/app/z3cform/converters.py\n+++ b/plone/app/z3cform/converters.py\n@@ -8,6 +8,7 @@\n from plone.app.z3cform.interfaces import IDateWidget\n from plone.app.z3cform.interfaces import ILinkWidget\n from plone.app.z3cform.interfaces import IQueryStringWidget\n+from plone.app.z3cform.interfaces import IRelatedItemsWidget\n from plone.app.z3cform.interfaces import ISelectWidget\n from plone.app.z3cform.interfaces import ISingleCheckBoxBoolWidget\n from plone.app.z3cform.interfaces import ITimeWidget\n@@ -329,6 +330,7 @@ def toFieldValue(self, value):\n \n \n # BBB\n+@adapter(IRelation, IRelatedItemsWidget)\n class RelationChoiceRelatedItemsWidgetConverter(\n RelationChoiceContentBrowserWidgetConverter\n ):\n@@ -413,6 +415,7 @@ def toFieldValue(self, value):\n \n \n # BBB\n+@adapter(ICollection, IRelatedItemsWidget)\n class RelatedItemsDataConverter(ContentBrowserDataConverter):\n """backwards compatibility"""\n \ndiff --git a/plone/app/z3cform/converters.zcml b/plone/app/z3cform/converters.zcml\nindex eabfba73..09668922 100644\n--- a/plone/app/z3cform/converters.zcml\n+++ b/plone/app/z3cform/converters.zcml\n@@ -23,4 +23,8 @@\n />\n \n \n+\n+ \n+ \n+ \n \n' + +Repository: plone.app.z3cform + + +Branch: refs/heads/master +Date: 2024-09-20T08:08:41+02:00 +Author: Peter Mathis (petschki) +Commit: https://github.com/plone/plone.app.z3cform/commit/d38e1fab87c1d66619950fd4cdd007a5ffd8f559 + +Merge pull request #197 from plone/pat-contentbrowser-widget + +Implement `ContentBrowserWidget` for the new `pat-contentbrowser` pattern Files changed: -A news/+meta.internal -M .meta.toml -M pyproject.toml +A news/197.feature +A plone/app/z3cform/templates/contentbrowser_display.pt +A plone/app/z3cform/widgets/contentbrowser.py +M plone/app/z3cform/converters.py +M plone/app/z3cform/converters.zcml +M plone/app/z3cform/interfaces.py +M plone/app/z3cform/templates/link_input.pt +M plone/app/z3cform/templates/relateditems_display.pt +M plone/app/z3cform/tests/test_widgets.py +M plone/app/z3cform/widgets.zcml -b'diff --git a/.meta.toml b/.meta.toml\nindex c2aed684..3e42afd1 100644\n--- a/.meta.toml\n+++ b/.meta.toml\n@@ -3,7 +3,7 @@\n # See the inline comments on how to expand/tweak this configuration file\n [meta]\n template = "default"\n-commit-id = "a89af8f2"\n+commit-id = "5d22fbf8"\n \n [pyproject]\n codespell_ignores = "hove"\ndiff --git a/news/+meta.internal b/news/+meta.internal\nnew file mode 100644\nindex 00000000..c08f5399\n--- /dev/null\n+++ b/news/+meta.internal\n@@ -0,0 +1,2 @@\n+Update configuration files.\n+[plone devs]\ndiff --git a/pyproject.toml b/pyproject.toml\nindex a6df4ae8..e229484f 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -119,6 +119,7 @@ Zope = [\n \'Products.CMFCore\', \'Products.CMFDynamicViewFTI\',\n ]\n python-dateutil = [\'dateutil\']\n+pytest-plone = [\'pytest\', \'zope.pytestlayer\', \'plone.testing\', \'plone.app.testing\']\n ignore-packages = [\'plone.app.content\', \'plone.app.relationfield\', \'plone.directives.form\', \'plone.directives.dexterity\', \'five.grok\', \'plone.app.intid\', \'plone.contentrules\', \'plone.schema\', \'z3c.relationfield\']\n \n ##\n' +b'diff --git a/news/197.feature b/news/197.feature\nnew file mode 100644\nindex 00000000..abc8225c\n--- /dev/null\n+++ b/news/197.feature\n@@ -0,0 +1,9 @@\n+Implement new `ContentBrowserWidget` for `pat-contentbrowser` pattern.\n+\n+The deprecated `RelatedItemsWidget` and `pat-relateditems` pattern is still available\n+and imports should not break. But the default widget and converter adapter registration for\n+z3c.relationfield is changed to the new widget.\n+\n+Since `plone.app.relationfield` defines the widget with `plone.autoform` schema\n+hints nothing changes until the package is updated to the new widget.\n+[petschki]\ndiff --git a/plone/app/z3cform/converters.py b/plone/app/z3cform/converters.py\nindex 1e824628..fb586c65 100644\n--- a/plone/app/z3cform/converters.py\n+++ b/plone/app/z3cform/converters.py\n@@ -3,6 +3,7 @@\n from datetime import time\n from plone.app.z3cform import utils\n from plone.app.z3cform.interfaces import IAjaxSelectWidget\n+from plone.app.z3cform.interfaces import IContentBrowserWidget\n from plone.app.z3cform.interfaces import IDatetimeWidget\n from plone.app.z3cform.interfaces import IDateWidget\n from plone.app.z3cform.interfaces import ILinkWidget\n@@ -304,9 +305,9 @@ def toFieldValue(self, value):\n return collectionType(untokenized_value)\n \n \n-@adapter(IRelation, IRelatedItemsWidget)\n-class RelationChoiceRelatedItemsWidgetConverter(BaseDataConverter):\n- """Data converter for RelationChoice fields using the RelatedItemsWidget."""\n+@adapter(IRelation, IContentBrowserWidget)\n+class RelationChoiceContentBrowserWidgetConverter(BaseDataConverter):\n+ """Data converter for RelationChoice fields using the ContentBrowserWidget."""\n \n def toWidgetValue(self, value):\n if not value:\n@@ -328,8 +329,16 @@ def toFieldValue(self, value):\n return self.field.missing_value\n \n \n+# BBB\n+@adapter(IRelation, IRelatedItemsWidget)\n+class RelationChoiceRelatedItemsWidgetConverter(\n+ RelationChoiceContentBrowserWidgetConverter\n+):\n+ """backwards compatibility"""\n+\n+\n @adapter(IRelation, ISequenceWidget)\n-class RelationChoiceSelectWidgetConverter(RelationChoiceRelatedItemsWidgetConverter):\n+class RelationChoiceSelectWidgetConverter(RelationChoiceContentBrowserWidgetConverter):\n """Data converter for RelationChoice fields using with SequenceWidgets,\n which expect sequence values.\n """\n@@ -341,9 +350,9 @@ def toWidgetValue(self, value):\n return [IUUID(value)]\n \n \n-@adapter(ICollection, IRelatedItemsWidget)\n-class RelatedItemsDataConverter(BaseDataConverter):\n- """Data converter for ICollection fields using the RelatedItemsWidget."""\n+@adapter(ICollection, IContentBrowserWidget)\n+class ContentBrowserDataConverter(BaseDataConverter):\n+ """Data converter for ICollection fields using the ContentBrowserWidget."""\n \n def toWidgetValue(self, value):\n """Converts from field value to widget.\n@@ -405,8 +414,14 @@ def toFieldValue(self, value):\n return collectionType(valueType(v) for v in value)\n \n \n+# BBB\n+@adapter(ICollection, IRelatedItemsWidget)\n+class RelatedItemsDataConverter(ContentBrowserDataConverter):\n+ """backwards compatibility"""\n+\n+\n @adapter(IRelationList, ISequenceWidget)\n-class RelationListSelectWidgetDataConverter(RelatedItemsDataConverter):\n+class RelationListSelectWidgetDataConverter(ContentBrowserDataConverter):\n """Data converter for RelationChoice fields using with SequenceWidgets,\n which expect sequence values.\n """\ndiff --git a/plone/app/z3cform/converters.zcml b/plone/app/z3cform/converters.zcml\nindex 5aca1fc2..09668922 100644\n--- a/plone/app/z3cform/converters.zcml\n+++ b/plone/app/z3cform/converters.zcml\n@@ -8,19 +8,23 @@\n \n \n \n- \n+ \n \n \n- \n+ \n \n \n \n+\n+ \n+ \n+ \n \ndiff --git a/plone/app/z3cform/interfaces.py b/plone/app/z3cform/interfaces.py\nindex 0c22c77a..9b4b065c 100644\n--- a/plone/app/z3cform/interfaces.py\n+++ b/plone/app/z3cform/interfaces.py\n@@ -91,6 +91,10 @@ class IRelatedItemsWidget(ITextWidget):\n """Marker interface for the RelatedItemsWidget."""\n \n \n+class IContentBrowserWidget(ITextWidget):\n+ """Marker interface for the RelatedItemsWidget."""\n+\n+\n class IRichTextWidget(patextfield_IRichTextWidget):\n """Marker interface for the TinyMCEWidget."""\n \ndiff --git a/plone/app/z3cform/templates/contentbrowser_display.pt b/plone/app/z3cform/templates/contentbrowser_display.pt\nnew file mode 100644\nindex 00000000..972a9702\n--- /dev/null\n+++ b/plone/app/z3cform/templates/contentbrowser_display.pt\n@@ -0,0 +1,44 @@\n+
\n+ \n+
\ndiff --git a/plone/app/z3cform/templates/link_input.pt b/plone/app/z3cform/templates/link_input.pt\nindex ff3e0f39..853760f0 100644\n--- a/plone/app/z3cform/templates/link_input.pt\n+++ b/plone/app/z3cform/templates/link_input.pt\n@@ -25,11 +25,11 @@\n
\n
\n \n- \n
\n
\ndiff --git a/plone/app/z3cform/templates/relateditems_display.pt b/plone/app/z3cform/templates/relateditems_display.pt\nindex b6203c2b..a3b19ea7 100644\n--- a/plone/app/z3cform/templates/relateditems_display.pt\n+++ b/plone/app/z3cform/templates/relateditems_display.pt\n@@ -1,46 +1,44 @@\n-\n-
\n-
\n-
\n+ \n+ \n+ \n+ \n+ \n+\ndiff --git a/plone/app/z3cform/tests/test_widgets.py b/plone/app/z3cform/tests/test_widgets.py\nindex 77438a0b..f453ba14 100644\n--- a/plone/app/z3cform/tests/test_widgets.py\n+++ b/plone/app/z3cform/tests/test_widgets.py\n@@ -8,8 +8,8 @@\n from plone.app.testing import TEST_USER_ID\n from plone.app.z3cform.tests.layer import PAZ3CForm_INTEGRATION_TESTING\n from plone.app.z3cform.widgets.base import PatternFormElement\n+from plone.app.z3cform.widgets.contentbrowser import ContentBrowserWidget\n from plone.app.z3cform.widgets.datetime import DateWidget\n-from plone.app.z3cform.widgets.relateditems import RelatedItemsWidget\n from plone.app.z3cform.widgets.text import TextFieldWidget\n from plone.autoform.directives import widget\n from plone.autoform.form import AutoExtensibleForm\n@@ -1407,7 +1407,7 @@ class IRelationsType(Interface):\n multiple = RelationList(title="Multiple (Relations field)", required=False)\n \n \n-class RelatedItemsWidgetTemplateIntegrationTests(unittest.TestCase):\n+class ContentBrowserWidgetTemplateIntegrationTests(unittest.TestCase):\n layer = PAZ3CForm_INTEGRATION_TESTING\n \n def setUp(self):\n@@ -1444,7 +1444,7 @@ def test_related_items_widget_display_template(self):\n default_view.update()\n \n single = default_view.w["single"]\n- self.assertIsInstance(single, RelatedItemsWidget)\n+ self.assertIsInstance(single, ContentBrowserWidget)\n self.assertTrue(single.value, target.UID())\n items = single.items()\n self.assertIsInstance(items, ContentListing)\n@@ -1463,7 +1463,7 @@ def test_related_items_widget_display_template(self):\n )\n \n multiple = default_view.w["multiple"]\n- self.assertIsInstance(multiple, RelatedItemsWidget)\n+ self.assertIsInstance(multiple, ContentBrowserWidget)\n self.assertTrue(multiple.value, ";".join([target.UID(), doc.UID()]))\n items = multiple.items()\n self.assertIsInstance(items, ContentListing)\n@@ -1649,6 +1649,168 @@ def test_fieldwidget(self):\n self.assertIs(widget.request, request)\n \n \n+class ContentBrowserWidgetTests(unittest.TestCase):\n+ layer = PAZ3CForm_INTEGRATION_TESTING\n+\n+ def setUp(self):\n+ self.portal = self.layer["portal"]\n+ self.request = self.layer["request"]\n+ setRoles(self.portal, TEST_USER_ID, ["Manager"])\n+\n+ def test_single_selection(self):\n+ """The pattern_options value for maximumSelectionSize should\n+ be 1 when the field only allows a single selection."""\n+ from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget\n+\n+ field = Choice(\n+ __name__="selectfield",\n+ values=["one", "two", "three"],\n+ )\n+ widget = ContentBrowserFieldWidget(field, self.request)\n+ widget.context = self.portal\n+ widget.update()\n+ pattern_options = widget.get_pattern_options()\n+ self.assertEqual(pattern_options.get("maximumSelectionSize", 0), 1)\n+\n+ def test_multiple_selection(self):\n+ """The pattern_options key maximumSelectionSize shouldn\'t be\n+ set when the field allows multiple selections"""\n+ from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget\n+ from Zope2.App.schema import Zope2VocabularyRegistry\n+ from zope.schema.interfaces import ISource\n+\n+ field = List(\n+ __name__="selectfield",\n+ value_type=Choice(vocabulary="foobar"),\n+ )\n+ widget = ContentBrowserFieldWidget(field, self.request)\n+ widget.context = self.portal\n+\n+ vocab = Mock()\n+ alsoProvides(vocab, ISource)\n+ with mock.patch.object(Zope2VocabularyRegistry, "get", return_value=vocab):\n+ widget.update()\n+ patterns_options = widget.get_pattern_options()\n+ self.assertFalse("maximumSelectionSize" in patterns_options)\n+ self.assertEqual(\n+ patterns_options["vocabularyUrl"],\n+ "http://nohost/plone/@@getVocabulary?name=foobar&field=selectfield",\n+ )\n+\n+ def test_converter_RelationChoice(self):\n+ from plone.app.z3cform.converters import (\n+ RelationChoiceContentBrowserWidgetConverter,\n+ )\n+\n+ brain = Mock(getObject=Mock(return_value="obj"))\n+ portal_catalog = Mock(return_value=[brain])\n+ widget = Mock()\n+ converter = RelationChoiceContentBrowserWidgetConverter(\n+ TextLine(),\n+ widget,\n+ )\n+\n+ with mock.patch(\n+ "plone.app.z3cform.converters.IUUID",\n+ return_value="id",\n+ ):\n+ self.assertEqual(converter.toWidgetValue("obj"), "id")\n+ self.assertEqual(converter.toWidgetValue(None), None)\n+\n+ with mock.patch(\n+ "plone.app.z3cform.converters.getToolByName",\n+ return_value=portal_catalog,\n+ ):\n+ self.assertEqual(converter.toFieldValue("id"), "obj")\n+ self.assertEqual(converter.toFieldValue(None), None)\n+\n+ def test_converter_RelationList(self):\n+ from plone.app.z3cform.converters import ContentBrowserDataConverter\n+ from z3c.relationfield.interfaces import IRelationList\n+\n+ field = List()\n+ alsoProvides(field, IRelationList)\n+ brain1 = Mock(getObject=Mock(return_value="obj1"), UID="id1")\n+ brain2 = Mock(getObject=Mock(return_value="obj2"), UID="id2")\n+ portal_catalog = Mock(return_value=[brain1, brain2])\n+ widget = Mock(separator=";")\n+ converter = ContentBrowserDataConverter(field, widget)\n+\n+ self.assertEqual(converter.toWidgetValue(None), None)\n+ with mock.patch(\n+ "plone.app.z3cform.converters.IUUID",\n+ side_effect=["id1", "id2"],\n+ ):\n+ self.assertEqual(\n+ converter.toWidgetValue(["obj1", "obj2"]),\n+ "id1;id2",\n+ )\n+\n+ self.assertEqual(converter.toFieldValue(None), None)\n+ with mock.patch(\n+ "plone.app.z3cform.converters.getToolByName",\n+ return_value=portal_catalog,\n+ ):\n+ self.assertEqual(\n+ converter.toFieldValue("id1;id2"),\n+ ["obj1", "obj2"],\n+ )\n+\n+ def test_converter_List_of_Choice(self):\n+ from plone.app.z3cform.converters import ContentBrowserDataConverter\n+\n+ fields = (\n+ List(),\n+ List(value_type=TextLine()),\n+ List(value_type=BytesLine()),\n+ List(value_type=Choice(values=["one", "two", "three"])),\n+ )\n+ for field in fields:\n+ expected_value_type = getattr(\n+ field.value_type,\n+ "_type",\n+ str,\n+ )\n+ if expected_value_type is None:\n+ expected_value_type = str\n+ widget = Mock(separator=";")\n+ converter = ContentBrowserDataConverter(field, widget)\n+\n+ self.assertEqual(converter.toWidgetValue(None), None)\n+ self.assertEqual(\n+ converter.toWidgetValue(["id1", "id2"]),\n+ "id1;id2",\n+ )\n+\n+ self.assertEqual(converter.toFieldValue(None), None)\n+ if expected_value_type == bytes:\n+ expected = [b"id1", b"id2"]\n+ else:\n+ expected = ["id1", "id2"]\n+ self.assertEqual(\n+ converter.toFieldValue("id1;id2"),\n+ expected,\n+ )\n+\n+ self.assertEqual(converter.toFieldValue(None), None)\n+ self.assertEqual(\n+ type(converter.toFieldValue("id1;id2")[0]),\n+ expected_value_type,\n+ )\n+\n+ def test_fieldwidget(self):\n+ from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget\n+ from plone.app.z3cform.widgets.contentbrowser import ContentBrowserWidget\n+\n+ field = Mock(__name__="field", title="", required=True)\n+ vocabulary = Mock()\n+ request = Mock()\n+ widget = ContentBrowserFieldWidget(field, vocabulary, request)\n+ self.assertTrue(isinstance(widget, ContentBrowserWidget))\n+ self.assertIs(widget.field, field)\n+ self.assertIs(widget.request, request)\n+\n+\n class RichTextWidgetTests(unittest.TestCase):\n layer = PAZ3CForm_INTEGRATION_TESTING\n \ndiff --git a/plone/app/z3cform/widgets.zcml b/plone/app/z3cform/widgets.zcml\nindex 5fd76ff2..fe65f2ca 100644\n--- a/plone/app/z3cform/widgets.zcml\n+++ b/plone/app/z3cform/widgets.zcml\n@@ -223,27 +223,36 @@\n \n \n \n+ \n+\n+ \n+\n \n \n \n \n \n \n