diff --git a/last_commit.txt b/last_commit.txt index 1f56416ab2..2d1e0818f6 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,70 +1,42 @@ -Repository: plone.app.contenttypes +Repository: plone.app.querystring Branch: refs/heads/master -Date: 2023-05-15T12:22:45+02:00 -Author: 1letter (1letter) <1letter@gmx.de> -Commit: https://github.com/plone/plone.app.contenttypes/commit/b8a16cb9813af4665b9805beb6805de0aeea6620 - -Update .gitignore - -Files changed: -M .gitignore - -b'diff --git a/.gitignore b/.gitignore\nindex f1dddbb2..59d8710d 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -30,3 +30,4 @@ docs/doctrees\n docs/html\n library-settings.txt\n pip-selfcheck.json\n+venv\n' - -Repository: plone.app.contenttypes - - -Branch: refs/heads/master -Date: 2023-05-15T13:05:05+02:00 -Author: 1letter (1letter) <1letter@gmx.de> -Commit: https://github.com/plone/plone.app.contenttypes/commit/7e9fbadc615220ffc2e0fe3b50bb2c527942a593 - -Fix for #626 - -extends the the calculation of url if resolveuid part of link in LinkRedirectView - -add a test - -Files changed: -M plone/app/contenttypes/browser/link_redirect_view.py -M plone/app/contenttypes/tests/test_link.py - -b'diff --git a/plone/app/contenttypes/browser/link_redirect_view.py b/plone/app/contenttypes/browser/link_redirect_view.py\nindex d780e96a..eee881ca 100644\n--- a/plone/app/contenttypes/browser/link_redirect_view.py\n+++ b/plone/app/contenttypes/browser/link_redirect_view.py\n@@ -108,6 +108,13 @@ def absolute_target_url(self):\n context_state = self.context.restrictedTraverse("@@plone_context_state")\n url = "/".join([context_state.canonical_object_url(), url])\n else:\n+ if "resolveuid" in url:\n+ uid = url.split("/")[-1]\n+ obj = uuidToObject(uid)\n+ if obj:\n+ url = "/".join(obj.getPhysicalPath()[2:])\n+ if not url.startswith("/"):\n+ url = "/" + url\n if not url.startswith(("http://", "https://")):\n url = self.request["SERVER_URL"] + url\n \ndiff --git a/plone/app/contenttypes/tests/test_link.py b/plone/app/contenttypes/tests/test_link.py\nindex a7abb033..dd306c27 100644\n--- a/plone/app/contenttypes/tests/test_link.py\n+++ b/plone/app/contenttypes/tests/test_link.py\n@@ -365,3 +365,14 @@ def test_var_replacement_in_view(self):\n self.link.remoteUrl = "${navigation_root_url}"\n self.assertEqual(view.url(), "/plone")\n self.assertEqual(view.absolute_target_url(), "http://nohost/plone")\n+\n+ def test_resolve_uid_to_absolute_target(self):\n+ view = getMultiAdapter((self.link, self.request), name="link_redirect_view")\n+\n+ self.portal.invokeFactory(\n+ "Document", "doc1", title="A document", description="This is a document."\n+ )\n+ doc1 = self.portal["doc1"]\n+ uid = IUUID(doc1)\n+ self.link.remoteUrl = f"${{portal_url}}/resolveuid/{uid}"\n+ self.assertEqual(view.absolute_target_url(), "http://nohost/doc1")\n' - -Repository: plone.app.contenttypes - - -Branch: refs/heads/master -Date: 2023-05-15T13:06:28+02:00 -Author: 1letter (1letter) <1letter@gmx.de> -Commit: https://github.com/plone/plone.app.contenttypes/commit/aeecbcd77943838c01df7796e139df7442816807 +Date: 2023-05-15T12:01:45+02:00 +Author: Jens W. Klein (jensens) +Commit: https://github.com/plone/plone.app.querystring/commit/d7fdfe9cb415d78c3cf7d0d72cb0e9e34c1dbab7 -add News +Fix a circular dependency to plone.app.vocabularies Files changed: -A news/626.bugfix +A news/fix-circular-dep-pavocabularies.bugfix +A plone/app/querystring/tests/testVocabularies.py +A plone/app/querystring/vocabularies.py +M plone/app/querystring/configure.zcml +M plone/app/querystring/queryparser.py +M setup.py -b'diff --git a/news/626.bugfix b/news/626.bugfix\nnew file mode 100644\nindex 000000000..c741bbe11\n--- /dev/null\n+++ b/news/626.bugfix\n@@ -0,0 +1 @@\n+extends the the calculation of url if resolveuid part of link in LinkRedirectView @1letter\n\\ No newline at end of file\n' +b'diff --git a/news/fix-circular-dep-pavocabularies.bugfix b/news/fix-circular-dep-pavocabularies.bugfix\nnew file mode 100644\nindex 0000000..c55954a\n--- /dev/null\n+++ b/news/fix-circular-dep-pavocabularies.bugfix\n@@ -0,0 +1,5 @@\n+Fix a circular transitive dependency to `plone.app.querystring`.\n+New direct dependency explicit on `plone.app.vocabularies`.\n+Move `plone.app.querystring.catalog.CatalogVocabularyFactory` to `.vocabularies`, move the ZCML to register the factory, move the the test.\n+Move `plone.app.querystring.utils.parse_query` with new name `parseAndModifyFormquery` to `.queryparser`.\n+[@jensens]\ndiff --git a/plone/app/querystring/configure.zcml b/plone/app/querystring/configure.zcml\nindex 92ee3ee..b1e7bbc 100644\n--- a/plone/app/querystring/configure.zcml\n+++ b/plone/app/querystring/configure.zcml\n@@ -64,4 +64,8 @@\n name="1000"\n component=".querymodifiers.modify_query_to_enforce_navigation_root"\n />\n+ \n \ndiff --git a/plone/app/querystring/queryparser.py b/plone/app/querystring/queryparser.py\nindex 7c6b0cd..d32e1ea 100644\n--- a/plone/app/querystring/queryparser.py\n+++ b/plone/app/querystring/queryparser.py\n@@ -1,3 +1,4 @@\n+from .interfaces import IParsedQueryIndexModifier\n from Acquisition import aq_parent\n from collections import namedtuple\n from DateTime import DateTime\n@@ -8,6 +9,7 @@\n from plone.registry.interfaces import IRegistry\n from plone.uuid.interfaces import IUUID\n from Products.CMFCore.utils import getToolByName\n+from zope.component import getUtilitiesFor\n from zope.component import getUtility\n from zope.dottedname.resolve import resolve\n \n@@ -67,6 +69,23 @@ def parseFormquery(context, formquery, sort_on=None, sort_order=None):\n return query\n \n \n+def parseAndModifyFormquery(context, query, sort_on=None, sort_order=None):\n+ parsedquery = parseFormquery(context, query, sort_on, sort_order)\n+\n+ index_modifiers = getUtilitiesFor(IParsedQueryIndexModifier)\n+ for name, modifier in index_modifiers:\n+ if name in parsedquery:\n+ new_name, query = modifier(parsedquery[name])\n+ parsedquery[name] = query\n+ # if a new index name has been returned, we need to replace\n+ # the native ones\n+ if name != new_name:\n+ del parsedquery[name]\n+ parsedquery[new_name] = query\n+\n+ return parsedquery\n+\n+\n # Query operators\n \n \ndiff --git a/plone/app/querystring/tests/testVocabularies.py b/plone/app/querystring/tests/testVocabularies.py\nnew file mode 100644\nindex 0000000..1447984\n--- /dev/null\n+++ b/plone/app/querystring/tests/testVocabularies.py\n@@ -0,0 +1,19 @@\n+from plone.app.vocabularies.tests.test_vocabularies import vocabSetUp\n+from plone.app.vocabularies.tests.test_vocabularies import vocabTearDown\n+\n+import doctest\n+import unittest\n+\n+\n+def test_suite():\n+ optionflags = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE\n+ return unittest.TestSuite(\n+ (\n+ doctest.DocTestSuite(\n+ "plone.app.vocabularies.catalog",\n+ setUp=vocabSetUp,\n+ tearDown=vocabTearDown,\n+ optionflags=optionflags,\n+ ),\n+ )\n+ )\ndiff --git a/plone/app/querystring/vocabularies.py b/plone/app/querystring/vocabularies.py\nnew file mode 100644\nindex 0000000..283dd19\n--- /dev/null\n+++ b/plone/app/querystring/vocabularies.py\n@@ -0,0 +1,63 @@\n+from .queryparser import parseAndModifyFormquery\n+from plone.app.vocabularies.catalog import CatalogVocabulary\n+from plone.base.navigationroot import get_navigation_root_object\n+from zope.component.hooks import getSite\n+from zope.interface import implementer\n+from zope.schema.interfaces import IVocabularyFactory\n+\n+\n+# this vocabulary is in this package by intend.\n+# since plone.app.querystring depends on plone.app.vocabularies\n+# we can not put it over there without creating a circular dependency.\n+\n+\n+@implementer(IVocabularyFactory)\n+class CatalogVocabularyFactory:\n+ """\n+ Test application of Navigation Root:\n+\n+ >>> from plone.app.vocabularies.tests.base import create_context\n+ >>> from plone.app.vocabularies.tests.base import DummyUrlTool\n+ >>> from plone.app.vocabularies.tests.base import DummyCatalog\n+ >>> class DummyPathCatalog(DummyCatalog):\n+ ... def __call__(self, **query):\n+ ... if \'path\' in query and \'query\' in query[\'path\']:\n+ ... return [v for v in self.values() if\n+ ... v.getPath().startswith(query[\'path\'][\'query\'])]\n+ ... return self.values()\n+ >>> catalog = DummyPathCatalog([\'/abcd\', \'/defg\', \'/dummy/sub-site\',\n+ ... \'/dummy/sub-site/ghij\'])\n+ >>> context = create_context()\n+ >>> context.portal_catalog = catalog\n+ >>> context.portal_url = DummyUrlTool(context)\n+ >>> factory = CatalogVocabularyFactory()\n+\n+ >>> sorted(t.token for t in factory(context))\n+ [\'/abcd\', \'/defg\', \'/dummy/sub-site\', \'/dummy/sub-site/ghij\']\n+\n+ >>> from plone.app.vocabularies.tests.base import DummyNavRoot\n+ >>> nav_root = DummyNavRoot(\'sub-site\', parent=context)\n+ >>> [t.token for t in factory(nav_root)]\n+ [\'/dummy/sub-site\', \'/dummy/sub-site/ghij\']\n+\n+ """\n+\n+ def __call__(self, context, query=None):\n+ parsed = {}\n+ if query:\n+ parsed = parseAndModifyFormquery(context, query["criteria"])\n+ if "sort_on" in query:\n+ parsed["sort_on"] = query["sort_on"]\n+ if "sort_order" in query:\n+ parsed["sort_order"] = str(query["sort_order"])\n+\n+ if "path" not in parsed:\n+ site = getSite()\n+ nav_root = get_navigation_root_object(context, site)\n+ site_path = site.getPhysicalPath()\n+ if nav_root and nav_root.getPhysicalPath() != site_path:\n+ parsed["path"] = {\n+ "query": "/".join(nav_root.getPhysicalPath()),\n+ "depth": -1,\n+ }\n+ return CatalogVocabulary.fromItems(parsed, context)\ndiff --git a/setup.py b/setup.py\nindex 326d53e..a410f0e 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -42,6 +42,7 @@\n "setuptools",\n "plone.app.contentlisting",\n "plone.app.registry>=1.1",\n+ "plone.app.vocabularies",\n "plone.base",\n "plone.batching",\n "plone.i18n",\n' -Repository: plone.app.contenttypes +Repository: plone.app.querystring Branch: refs/heads/master -Date: 2023-05-17T01:55:04+02:00 +Date: 2023-05-17T11:43:13+02:00 Author: Jens W. Klein (jensens) -Commit: https://github.com/plone/plone.app.contenttypes/commit/229816c9a325b2b0bdd63b96e1f9c52f3278bfd8 +Commit: https://github.com/plone/plone.app.querystring/commit/45e36c40636dfb43d5bf403461e6435c022f09e8 -Merge pull request #662 from plone/1letter/fix-#626 +Merge pull request #124 from plone/fix-circular-dep-pavocabularies -1letter/fix #626 +Fix a circular dependency to plone.app.vocabularies Files changed: -A news/626.bugfix -M .gitignore -M plone/app/contenttypes/browser/link_redirect_view.py -M plone/app/contenttypes/tests/test_link.py - -b'diff --git a/.gitignore b/.gitignore\nindex f1dddbb23..59d8710d5 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -30,3 +30,4 @@ docs/doctrees\n docs/html\n library-settings.txt\n pip-selfcheck.json\n+venv\ndiff --git a/news/626.bugfix b/news/626.bugfix\nnew file mode 100644\nindex 000000000..c741bbe11\n--- /dev/null\n+++ b/news/626.bugfix\n@@ -0,0 +1 @@\n+extends the the calculation of url if resolveuid part of link in LinkRedirectView @1letter\n\\ No newline at end of file\ndiff --git a/plone/app/contenttypes/browser/link_redirect_view.py b/plone/app/contenttypes/browser/link_redirect_view.py\nindex d780e96ad..eee881cae 100644\n--- a/plone/app/contenttypes/browser/link_redirect_view.py\n+++ b/plone/app/contenttypes/browser/link_redirect_view.py\n@@ -108,6 +108,13 @@ def absolute_target_url(self):\n context_state = self.context.restrictedTraverse("@@plone_context_state")\n url = "/".join([context_state.canonical_object_url(), url])\n else:\n+ if "resolveuid" in url:\n+ uid = url.split("/")[-1]\n+ obj = uuidToObject(uid)\n+ if obj:\n+ url = "/".join(obj.getPhysicalPath()[2:])\n+ if not url.startswith("/"):\n+ url = "/" + url\n if not url.startswith(("http://", "https://")):\n url = self.request["SERVER_URL"] + url\n \ndiff --git a/plone/app/contenttypes/tests/test_link.py b/plone/app/contenttypes/tests/test_link.py\nindex a7abb0336..dd306c274 100644\n--- a/plone/app/contenttypes/tests/test_link.py\n+++ b/plone/app/contenttypes/tests/test_link.py\n@@ -365,3 +365,14 @@ def test_var_replacement_in_view(self):\n self.link.remoteUrl = "${navigation_root_url}"\n self.assertEqual(view.url(), "/plone")\n self.assertEqual(view.absolute_target_url(), "http://nohost/plone")\n+\n+ def test_resolve_uid_to_absolute_target(self):\n+ view = getMultiAdapter((self.link, self.request), name="link_redirect_view")\n+\n+ self.portal.invokeFactory(\n+ "Document", "doc1", title="A document", description="This is a document."\n+ )\n+ doc1 = self.portal["doc1"]\n+ uid = IUUID(doc1)\n+ self.link.remoteUrl = f"${{portal_url}}/resolveuid/{uid}"\n+ self.assertEqual(view.absolute_target_url(), "http://nohost/doc1")\n' +A news/fix-circular-dep-pavocabularies.bugfix +A plone/app/querystring/tests/testVocabularies.py +A plone/app/querystring/vocabularies.py +M plone/app/querystring/configure.zcml +M plone/app/querystring/queryparser.py +M setup.py + +b'diff --git a/news/fix-circular-dep-pavocabularies.bugfix b/news/fix-circular-dep-pavocabularies.bugfix\nnew file mode 100644\nindex 0000000..c55954a\n--- /dev/null\n+++ b/news/fix-circular-dep-pavocabularies.bugfix\n@@ -0,0 +1,5 @@\n+Fix a circular transitive dependency to `plone.app.querystring`.\n+New direct dependency explicit on `plone.app.vocabularies`.\n+Move `plone.app.querystring.catalog.CatalogVocabularyFactory` to `.vocabularies`, move the ZCML to register the factory, move the the test.\n+Move `plone.app.querystring.utils.parse_query` with new name `parseAndModifyFormquery` to `.queryparser`.\n+[@jensens]\ndiff --git a/plone/app/querystring/configure.zcml b/plone/app/querystring/configure.zcml\nindex 92ee3ee..b1e7bbc 100644\n--- a/plone/app/querystring/configure.zcml\n+++ b/plone/app/querystring/configure.zcml\n@@ -64,4 +64,8 @@\n name="1000"\n component=".querymodifiers.modify_query_to_enforce_navigation_root"\n />\n+ \n \ndiff --git a/plone/app/querystring/queryparser.py b/plone/app/querystring/queryparser.py\nindex 7c6b0cd..d32e1ea 100644\n--- a/plone/app/querystring/queryparser.py\n+++ b/plone/app/querystring/queryparser.py\n@@ -1,3 +1,4 @@\n+from .interfaces import IParsedQueryIndexModifier\n from Acquisition import aq_parent\n from collections import namedtuple\n from DateTime import DateTime\n@@ -8,6 +9,7 @@\n from plone.registry.interfaces import IRegistry\n from plone.uuid.interfaces import IUUID\n from Products.CMFCore.utils import getToolByName\n+from zope.component import getUtilitiesFor\n from zope.component import getUtility\n from zope.dottedname.resolve import resolve\n \n@@ -67,6 +69,23 @@ def parseFormquery(context, formquery, sort_on=None, sort_order=None):\n return query\n \n \n+def parseAndModifyFormquery(context, query, sort_on=None, sort_order=None):\n+ parsedquery = parseFormquery(context, query, sort_on, sort_order)\n+\n+ index_modifiers = getUtilitiesFor(IParsedQueryIndexModifier)\n+ for name, modifier in index_modifiers:\n+ if name in parsedquery:\n+ new_name, query = modifier(parsedquery[name])\n+ parsedquery[name] = query\n+ # if a new index name has been returned, we need to replace\n+ # the native ones\n+ if name != new_name:\n+ del parsedquery[name]\n+ parsedquery[new_name] = query\n+\n+ return parsedquery\n+\n+\n # Query operators\n \n \ndiff --git a/plone/app/querystring/tests/testVocabularies.py b/plone/app/querystring/tests/testVocabularies.py\nnew file mode 100644\nindex 0000000..1447984\n--- /dev/null\n+++ b/plone/app/querystring/tests/testVocabularies.py\n@@ -0,0 +1,19 @@\n+from plone.app.vocabularies.tests.test_vocabularies import vocabSetUp\n+from plone.app.vocabularies.tests.test_vocabularies import vocabTearDown\n+\n+import doctest\n+import unittest\n+\n+\n+def test_suite():\n+ optionflags = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE\n+ return unittest.TestSuite(\n+ (\n+ doctest.DocTestSuite(\n+ "plone.app.vocabularies.catalog",\n+ setUp=vocabSetUp,\n+ tearDown=vocabTearDown,\n+ optionflags=optionflags,\n+ ),\n+ )\n+ )\ndiff --git a/plone/app/querystring/vocabularies.py b/plone/app/querystring/vocabularies.py\nnew file mode 100644\nindex 0000000..283dd19\n--- /dev/null\n+++ b/plone/app/querystring/vocabularies.py\n@@ -0,0 +1,63 @@\n+from .queryparser import parseAndModifyFormquery\n+from plone.app.vocabularies.catalog import CatalogVocabulary\n+from plone.base.navigationroot import get_navigation_root_object\n+from zope.component.hooks import getSite\n+from zope.interface import implementer\n+from zope.schema.interfaces import IVocabularyFactory\n+\n+\n+# this vocabulary is in this package by intend.\n+# since plone.app.querystring depends on plone.app.vocabularies\n+# we can not put it over there without creating a circular dependency.\n+\n+\n+@implementer(IVocabularyFactory)\n+class CatalogVocabularyFactory:\n+ """\n+ Test application of Navigation Root:\n+\n+ >>> from plone.app.vocabularies.tests.base import create_context\n+ >>> from plone.app.vocabularies.tests.base import DummyUrlTool\n+ >>> from plone.app.vocabularies.tests.base import DummyCatalog\n+ >>> class DummyPathCatalog(DummyCatalog):\n+ ... def __call__(self, **query):\n+ ... if \'path\' in query and \'query\' in query[\'path\']:\n+ ... return [v for v in self.values() if\n+ ... v.getPath().startswith(query[\'path\'][\'query\'])]\n+ ... return self.values()\n+ >>> catalog = DummyPathCatalog([\'/abcd\', \'/defg\', \'/dummy/sub-site\',\n+ ... \'/dummy/sub-site/ghij\'])\n+ >>> context = create_context()\n+ >>> context.portal_catalog = catalog\n+ >>> context.portal_url = DummyUrlTool(context)\n+ >>> factory = CatalogVocabularyFactory()\n+\n+ >>> sorted(t.token for t in factory(context))\n+ [\'/abcd\', \'/defg\', \'/dummy/sub-site\', \'/dummy/sub-site/ghij\']\n+\n+ >>> from plone.app.vocabularies.tests.base import DummyNavRoot\n+ >>> nav_root = DummyNavRoot(\'sub-site\', parent=context)\n+ >>> [t.token for t in factory(nav_root)]\n+ [\'/dummy/sub-site\', \'/dummy/sub-site/ghij\']\n+\n+ """\n+\n+ def __call__(self, context, query=None):\n+ parsed = {}\n+ if query:\n+ parsed = parseAndModifyFormquery(context, query["criteria"])\n+ if "sort_on" in query:\n+ parsed["sort_on"] = query["sort_on"]\n+ if "sort_order" in query:\n+ parsed["sort_order"] = str(query["sort_order"])\n+\n+ if "path" not in parsed:\n+ site = getSite()\n+ nav_root = get_navigation_root_object(context, site)\n+ site_path = site.getPhysicalPath()\n+ if nav_root and nav_root.getPhysicalPath() != site_path:\n+ parsed["path"] = {\n+ "query": "/".join(nav_root.getPhysicalPath()),\n+ "depth": -1,\n+ }\n+ return CatalogVocabulary.fromItems(parsed, context)\ndiff --git a/setup.py b/setup.py\nindex 326d53e..a410f0e 100644\n--- a/setup.py\n+++ b/setup.py\n@@ -42,6 +42,7 @@\n "setuptools",\n "plone.app.contentlisting",\n "plone.app.registry>=1.1",\n+ "plone.app.vocabularies",\n "plone.base",\n "plone.batching",\n "plone.i18n",\n'