From d5abe9833d7bbccae6649a48b7180e26cbe0b07d Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Thu, 25 Jan 2024 16:30:24 +0100 Subject: [PATCH 1/8] Add getObjSize info in File field serializer --- CHANGES.rst | 3 ++- .../contenttypes/restapi/serializers/dxfields.py | 11 +++++++++-- .../tests/test_filefield_view_mode_serializer.py | 5 ++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 27756c9d..4306f573 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,8 @@ Changelog 6.1.11 (unreleased) ------------------- -- Nothing changed yet. +- Add getObjSize info in File field serializer. + [cekk] 6.1.10 (2024-01-16) diff --git a/src/design/plone/contenttypes/restapi/serializers/dxfields.py b/src/design/plone/contenttypes/restapi/serializers/dxfields.py index 1cf1ab10..133351c6 100644 --- a/src/design/plone/contenttypes/restapi/serializers/dxfields.py +++ b/src/design/plone/contenttypes/restapi/serializers/dxfields.py @@ -5,6 +5,7 @@ from design.plone.contenttypes.interfaces.servizio import IServizio from plone import api from plone.app.contenttypes.utils import replace_link_variables_by_paths +from plone.base.utils import human_readable_size from plone.dexterity.interfaces import IDexterityContent from plone.namedfile.interfaces import INamedFileField from plone.outputfilters.browser.resolveuid import uuidToURL @@ -56,7 +57,11 @@ def __call__(self): @adapter(INamedFileField, IDexterityContent, IDesignPloneContenttypesLayer) class FileFieldViewModeSerializer(DefaultFieldSerializer): - """Ovveride the basic DX serializer to handle the visualize file functionality""" + """ + Ovveride the basic DX serializer to: + - handle the visualize file functionality + - add getObjSize info + """ def __call__(self): namedfile = self.field.get(self.context) @@ -70,10 +75,12 @@ def __call__(self): self.field.__name__, ) ) + size = namedfile.getSize() result = { "filename": namedfile.filename, "content-type": namedfile.contentType, - "size": namedfile.getSize(), + "size": size, + "getObjSize": human_readable_size(size), "download": url, } diff --git a/src/design/plone/contenttypes/tests/test_filefield_view_mode_serializer.py b/src/design/plone/contenttypes/tests/test_filefield_view_mode_serializer.py index c0fb9737..b1532b2d 100644 --- a/src/design/plone/contenttypes/tests/test_filefield_view_mode_serializer.py +++ b/src/design/plone/contenttypes/tests/test_filefield_view_mode_serializer.py @@ -59,5 +59,8 @@ def test_if_visualize_files_true_so_dsiplay(self): commit() response = self.api_session.get(self.modulo.absolute_url()).json() - self.assertIn("@@display-file", response["file_principale"]["download"]) + + def test_human_readable_obj_size_in_data(self): + response = self.api_session.get(self.modulo.absolute_url()).json() + self.assertEqual("1 KB", response["file_principale"]["getObjSize"]) From f14babd8eff38c6f99d8919aa652648c02b815a3 Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Fri, 26 Jan 2024 12:11:01 +0100 Subject: [PATCH 2/8] add collective.volto.enhancedlinks dependency (needed for slate integration) --- CHANGES.rst | 4 +++- base.cfg | 3 ++- setup.py | 1 + .../profiles/default/metadata.xml | 3 ++- .../profiles/default/types/Modulo.xml | 1 + .../restapi/serializers/dxfields.py | 16 ++++++++++++++- src/design/plone/contenttypes/testing.py | 3 +++ .../contenttypes/tests/test_ct_modulo.py | 5 +++-- ...py => test_filefield_custom_serializer.py} | 9 +++++++-- .../contenttypes/upgrades/configure.zcml | 10 ++++++++++ .../plone/contenttypes/upgrades/upgrades.py | 20 +++++++++++++++++++ 11 files changed, 67 insertions(+), 8 deletions(-) rename src/design/plone/contenttypes/tests/{test_filefield_view_mode_serializer.py => test_filefield_custom_serializer.py} (83%) diff --git a/CHANGES.rst b/CHANGES.rst index 4306f573..155fb5f8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,9 @@ Changelog 6.1.11 (unreleased) ------------------- -- Add getObjSize info in File field serializer. +- Add collective.volto.enhancedlinks dependency (needed for slate integration). + [cekk] +- Add enhancedlinks infos in File field serializer. [cekk] diff --git a/base.cfg b/base.cfg index 7aaa4b1b..cd203669 100644 --- a/base.cfg +++ b/base.cfg @@ -33,7 +33,7 @@ environment-vars = eggs = Plone Pillow - design.plone.contenttypes [test] + design.plone.contenttypes [test,enhancedlinks] zcml-additional = - 7021 + 7030 profile-redturtle.bandi:default profile-collective.venue:default @@ -8,5 +8,6 @@ profile-eea.api.taxonomy:default profile-collective.z3cform.datagridfield:default profile-design.plone.contenttypes:taxonomy + profile-collective.volto.enhancedlinks:default diff --git a/src/design/plone/contenttypes/profiles/default/types/Modulo.xml b/src/design/plone/contenttypes/profiles/default/types/Modulo.xml index fe0ea9c1..1978e988 100644 --- a/src/design/plone/contenttypes/profiles/default/types/Modulo.xml +++ b/src/design/plone/contenttypes/profiles/default/types/Modulo.xml @@ -43,6 +43,7 @@ + diff --git a/src/design/plone/contenttypes/restapi/serializers/dxfields.py b/src/design/plone/contenttypes/restapi/serializers/dxfields.py index 133351c6..a03927c5 100644 --- a/src/design/plone/contenttypes/restapi/serializers/dxfields.py +++ b/src/design/plone/contenttypes/restapi/serializers/dxfields.py @@ -23,6 +23,13 @@ from zope.schema.interfaces import ISourceText from zope.schema.interfaces import ITextLine +try: + from collective.volto.enhancedlinks.interfaces import IEnhancedLinksEnabled + + HAS_ENHANCEDLINKS = True +except ImportError: + HAS_ENHANCEDLINKS = False + import json import re @@ -80,9 +87,16 @@ def __call__(self): "filename": namedfile.filename, "content-type": namedfile.contentType, "size": size, - "getObjSize": human_readable_size(size), "download": url, } + if HAS_ENHANCEDLINKS: + if IEnhancedLinksEnabled.providedBy(self.context): + result.update( + { + "getObjSize": human_readable_size(size), + "enhanced_links_enabled": True, + } + ) return json_compatible(result) diff --git a/src/design/plone/contenttypes/testing.py b/src/design/plone/contenttypes/testing.py index 035a1fec..f7284719 100644 --- a/src/design/plone/contenttypes/testing.py +++ b/src/design/plone/contenttypes/testing.py @@ -14,6 +14,7 @@ import collective.venue import collective.volto.blocksfield import collective.volto.cookieconsent +import collective.volto.enhancedlinks import collective.z3cform.datagridfield import design.plone.contenttypes import eea.api.taxonomy @@ -32,6 +33,7 @@ def setUpZope(self, app, configurationContext): super().setUpZope(app, configurationContext) self.loadZCML(package=collective.venue) self.loadZCML(package=collective.volto.blocksfield) + self.loadZCML(package=collective.volto.enhancedlinks) self.loadZCML(package=design.plone.contenttypes, context=configurationContext) self.loadZCML(package=plone.formwidget.geolocation) self.loadZCML(name="overrides.zcml", package=design.plone.contenttypes) @@ -71,6 +73,7 @@ def setUpZope(self, app, configurationContext): super().setUpZope(app, configurationContext) self.loadZCML(package=collective.venue) self.loadZCML(package=collective.volto.blocksfield) + self.loadZCML(package=collective.volto.enhancedlinks) self.loadZCML(package=design.plone.contenttypes, context=configurationContext) self.loadZCML(package=plone.formwidget.geolocation) self.loadZCML(package=eea.api.taxonomy) diff --git a/src/design/plone/contenttypes/tests/test_ct_modulo.py b/src/design/plone/contenttypes/tests/test_ct_modulo.py index e12bf364..e2bfdab0 100644 --- a/src/design/plone/contenttypes/tests/test_ct_modulo.py +++ b/src/design/plone/contenttypes/tests/test_ct_modulo.py @@ -8,14 +8,14 @@ import unittest -class TestDocument(unittest.TestCase): +class TestModulo(unittest.TestCase): layer = DESIGN_PLONE_CONTENTTYPES_INTEGRATION_TESTING def setUp(self): """Custom shared utility setup for tests.""" self.portal = self.layer["portal"] - def test_behaviors_enabled_for_documento(self): + def test_behaviors_enabled_for_modulo(self): portal_types = api.portal.get_tool(name="portal_types") self.assertEqual( portal_types["Modulo"].behaviors, @@ -29,5 +29,6 @@ def test_behaviors_enabled_for_documento(self): "plone.locking", "design.plone.contenttypes.behavior.multi_file", "plone.translatable", + "volto.enhanced_links_enabled", ), ) diff --git a/src/design/plone/contenttypes/tests/test_filefield_view_mode_serializer.py b/src/design/plone/contenttypes/tests/test_filefield_custom_serializer.py similarity index 83% rename from src/design/plone/contenttypes/tests/test_filefield_view_mode_serializer.py rename to src/design/plone/contenttypes/tests/test_filefield_custom_serializer.py index b1532b2d..2f478e86 100644 --- a/src/design/plone/contenttypes/tests/test_filefield_view_mode_serializer.py +++ b/src/design/plone/contenttypes/tests/test_filefield_custom_serializer.py @@ -14,7 +14,7 @@ import unittest -class SummarySerializerTest(unittest.TestCase): +class FileFieldSerializerTest(unittest.TestCase): layer = DESIGN_PLONE_CONTENTTYPES_API_FUNCTIONAL_TESTING def setUp(self): @@ -61,6 +61,11 @@ def test_if_visualize_files_true_so_dsiplay(self): response = self.api_session.get(self.modulo.absolute_url()).json() self.assertIn("@@display-file", response["file_principale"]["download"]) - def test_human_readable_obj_size_in_data(self): + def test_if_enhancedlinks_behavior_active_has_human_readable_obj_size_in_data(self): response = self.api_session.get(self.modulo.absolute_url()).json() self.assertEqual("1 KB", response["file_principale"]["getObjSize"]) + + def test_if_enhancedlinks_behavior_active_has_flag_in_data(self): + response = self.api_session.get(self.modulo.absolute_url()).json() + self.assertIn("enhanced_links_enabled", response["file_principale"]) + self.assertTrue(response["file_principale"]["enhanced_links_enabled"]) diff --git a/src/design/plone/contenttypes/upgrades/configure.zcml b/src/design/plone/contenttypes/upgrades/configure.zcml index e8fc5e60..541555aa 100644 --- a/src/design/plone/contenttypes/upgrades/configure.zcml +++ b/src/design/plone/contenttypes/upgrades/configure.zcml @@ -804,4 +804,14 @@ handler=".upgrades.update_pdc_with_pdc_desc" /> + + + diff --git a/src/design/plone/contenttypes/upgrades/upgrades.py b/src/design/plone/contenttypes/upgrades/upgrades.py index 6019d520..1ccf0148 100644 --- a/src/design/plone/contenttypes/upgrades/upgrades.py +++ b/src/design/plone/contenttypes/upgrades/upgrades.py @@ -1573,3 +1573,23 @@ def update_pdc_with_pdc_desc(context): v["pdc_desc"] = None logger.info(f"Set pdc_desc for {pdc.absolute_url()}") logger.info("Ends of update") + + +def to_7030(context): + installOrReinstallProduct(api.portal.get(), "collective.volto.enhancedlinks") + # add behavior to modulo + portal_types = api.portal.get_tool(name="portal_types") + modulo_behaviors = [x for x in portal_types["Modulo"].behaviors] + if "volto.enhanced_links_enabled" not in modulo_behaviors: + modulo_behaviors.append("volto.enhanced_links_enabled") + portal_types["Modulo"].behaviors = tuple(modulo_behaviors) + + # update index/metadata + brains = api.content.find(portal_type=["File", "Image", "Modulo"]) + tot = len(brains) + i = 0 + for brain in brains: + i += 1 + if i % 100 == 0: + logger.info("Progress: {}/{}".format(i, tot)) + brain.getObject().reindexObject(idxs=["enhanced_links_enabled"]) From fa4cf9f3861a732323fe35236c07e6442ab1593c Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Fri, 26 Jan 2024 14:19:28 +0100 Subject: [PATCH 3/8] remove customizations --- CHANGES.rst | 6 ++---- base.cfg | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 512657fb..bcbe67be 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,15 +4,13 @@ Changelog 6.1.11 (unreleased) ------------------- +- Fixed script to update pdc with description + [eikichi18] - Add collective.volto.enhancedlinks dependency (needed for slate integration). [cekk] - Add enhancedlinks infos in File field serializer. -- Fixed script to update pdc with description - [eikichi18] -- Add getObjSize info in File field serializer. [cekk] - 6.1.10 (2024-01-16) ------------------- diff --git a/base.cfg b/base.cfg index cd203669..20b969de 100644 --- a/base.cfg +++ b/base.cfg @@ -33,7 +33,7 @@ environment-vars = eggs = Plone Pillow - design.plone.contenttypes [test,enhancedlinks] + design.plone.contenttypes [test] zcml-additional = Date: Mon, 29 Jan 2024 16:37:13 +0100 Subject: [PATCH 4/8] add info also for image fields --- .../restapi/serializers/configure.zcml | 1 + .../restapi/serializers/dxfields.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/design/plone/contenttypes/restapi/serializers/configure.zcml b/src/design/plone/contenttypes/restapi/serializers/configure.zcml index d5c46918..e81c934a 100644 --- a/src/design/plone/contenttypes/restapi/serializers/configure.zcml +++ b/src/design/plone/contenttypes/restapi/serializers/configure.zcml @@ -22,6 +22,7 @@ + diff --git a/src/design/plone/contenttypes/restapi/serializers/dxfields.py b/src/design/plone/contenttypes/restapi/serializers/dxfields.py index a03927c5..8fe64a06 100644 --- a/src/design/plone/contenttypes/restapi/serializers/dxfields.py +++ b/src/design/plone/contenttypes/restapi/serializers/dxfields.py @@ -8,12 +8,16 @@ from plone.base.utils import human_readable_size from plone.dexterity.interfaces import IDexterityContent from plone.namedfile.interfaces import INamedFileField +from plone.namedfile.interfaces import INamedImageField from plone.outputfilters.browser.resolveuid import uuidToURL from plone.restapi.interfaces import IBlockFieldSerializationTransformer from plone.restapi.interfaces import IFieldSerializer from plone.restapi.interfaces import ISerializeToJsonSummary from plone.restapi.serializer.converters import json_compatible from plone.restapi.serializer.dxfields import DefaultFieldSerializer +from plone.restapi.serializer.dxfields import ( + ImageFieldSerializer as BaseImageFieldSerializer, +) from zope.component import adapter from zope.component import getMultiAdapter from zope.component import subscribers @@ -109,6 +113,21 @@ def get_file_view_mode(self, content_type): return "@@download" +@adapter(INamedImageField, IDexterityContent, IDesignPloneContenttypesLayer) +class ImageFieldSerializer(BaseImageFieldSerializer): + def __call__(self): + result = super().__call__() + if HAS_ENHANCEDLINKS: + if IEnhancedLinksEnabled.providedBy(self.context): + result.update( + { + "getObjSize": human_readable_size(result["size"]), + "enhanced_links_enabled": True, + } + ) + return result + + def serialize_data(context, json_data, show_children=False): request = getRequest() if not json_data: From 6d5fbffeb22dd5b19950b70a81b8f7153bcb5386 Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Tue, 30 Jan 2024 14:47:01 +0100 Subject: [PATCH 5/8] fix imports --- .../contenttypes/adapters/configure.zcml | 1 + .../restapi/serializers/dxfields.py | 37 ++++++++----------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/design/plone/contenttypes/adapters/configure.zcml b/src/design/plone/contenttypes/adapters/configure.zcml index 8f7a3224..7e7188fd 100644 --- a/src/design/plone/contenttypes/adapters/configure.zcml +++ b/src/design/plone/contenttypes/adapters/configure.zcml @@ -20,4 +20,5 @@ factory=".searchabletext_indexers.TextBlockSearchableText" name="text" /> + diff --git a/src/design/plone/contenttypes/restapi/serializers/dxfields.py b/src/design/plone/contenttypes/restapi/serializers/dxfields.py index 8fe64a06..ef0be61e 100644 --- a/src/design/plone/contenttypes/restapi/serializers/dxfields.py +++ b/src/design/plone/contenttypes/restapi/serializers/dxfields.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from AccessControl.unauthorized import Unauthorized from Acquisition import aq_inner +from collective.volto.enhancedlinks.interfaces import IEnhancedLinksEnabled from design.plone.contenttypes.interfaces import IDesignPloneContenttypesLayer from design.plone.contenttypes.interfaces.servizio import IServizio from plone import api @@ -27,12 +28,6 @@ from zope.schema.interfaces import ISourceText from zope.schema.interfaces import ITextLine -try: - from collective.volto.enhancedlinks.interfaces import IEnhancedLinksEnabled - - HAS_ENHANCEDLINKS = True -except ImportError: - HAS_ENHANCEDLINKS = False import json import re @@ -93,14 +88,13 @@ def __call__(self): "size": size, "download": url, } - if HAS_ENHANCEDLINKS: - if IEnhancedLinksEnabled.providedBy(self.context): - result.update( - { - "getObjSize": human_readable_size(size), - "enhanced_links_enabled": True, - } - ) + if IEnhancedLinksEnabled.providedBy(self.context): + result.update( + { + "getObjSize": human_readable_size(size), + "enhanced_links_enabled": True, + } + ) return json_compatible(result) @@ -117,14 +111,13 @@ def get_file_view_mode(self, content_type): class ImageFieldSerializer(BaseImageFieldSerializer): def __call__(self): result = super().__call__() - if HAS_ENHANCEDLINKS: - if IEnhancedLinksEnabled.providedBy(self.context): - result.update( - { - "getObjSize": human_readable_size(result["size"]), - "enhanced_links_enabled": True, - } - ) + if IEnhancedLinksEnabled.providedBy(self.context): + result.update( + { + "getObjSize": human_readable_size(result["size"]), + "enhanced_links_enabled": True, + } + ) return result From a3d027903d77131a91a99d2ef6e57751191b7614 Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Wed, 31 Jan 2024 14:50:15 +0100 Subject: [PATCH 6/8] enhance Bandi serializer for enhancedlinks features --- src/design/plone/contenttypes/restapi/serializers/bando.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/design/plone/contenttypes/restapi/serializers/bando.py b/src/design/plone/contenttypes/restapi/serializers/bando.py index 9e463ad0..c3354e70 100644 --- a/src/design/plone/contenttypes/restapi/serializers/bando.py +++ b/src/design/plone/contenttypes/restapi/serializers/bando.py @@ -19,6 +19,11 @@ def get_approfondimenti(self, bando_view): contents = bando_view.retrieveContentsOfFolderDeepening(folder["path"]) if not contents: continue + # fix results for enhancedlinks + for content in contents: + content["getObjSize"] = content.get("filesize", "") + content["mime_type"] = content.get("content-type", "") + content["enhanced_links_enabled"] = "filesize" in content folder.update({"children": contents}) results.append(folder) return results From 586269c37a26393ea8f9e532ad17bf3c1de121d6 Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Fri, 1 Mar 2024 09:58:42 +0100 Subject: [PATCH 7/8] fix upgrade-step and changelog --- CHANGES.rst | 4 +- .../contenttypes/upgrades/configure.zcml | 12 +++++- .../plone/contenttypes/upgrades/upgrades.py | 38 +++++++++---------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index cfe14f06..260033fe 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,8 @@ Changelog 6.1.15 (unreleased) ------------------- -- Nothing changed yet. +- Add dependency with collective.volto.enhancedlinks. + [cekk] 6.1.14 (2024-02-20) @@ -19,7 +20,6 @@ Changelog [eikichi18] - 6.1.13 (2024-02-08) ------------------- diff --git a/src/design/plone/contenttypes/upgrades/configure.zcml b/src/design/plone/contenttypes/upgrades/configure.zcml index fc780738..ab514c8c 100644 --- a/src/design/plone/contenttypes/upgrades/configure.zcml +++ b/src/design/plone/contenttypes/upgrades/configure.zcml @@ -828,4 +828,14 @@ handler=".upgrades.to_7031" /> - \ No newline at end of file + + + + diff --git a/src/design/plone/contenttypes/upgrades/upgrades.py b/src/design/plone/contenttypes/upgrades/upgrades.py index c34b734b..81d655d6 100644 --- a/src/design/plone/contenttypes/upgrades/upgrades.py +++ b/src/design/plone/contenttypes/upgrades/upgrades.py @@ -1579,6 +1579,25 @@ def update_pdc_with_pdc_desc(context): logger.info("Ends of update") +def add_canale_digitale_link_index(context): + update_catalog(context) + update_registry(context) + brains = api.content.find(portal_type="Servizio") + logger.info(f"Found {len(brains)} Servizio content type to reindex") + for brain in brains: + service = brain.getObject() + service.reindexObject(idxs=["canale_digitale_link"]) + logger.info(f"Reindexed {service.absolute_url()}") + logger.info("End of update, added index canale_digitale_link") + + +def to_7031(context): + portal_types = api.portal.get_tool(name="portal_types") + for ptype in ["News Item"]: + portal_types[ptype].default_view = "view" + portal_types[ptype].view_methods = ["view"] + + def to_7030(context): installOrReinstallProduct(api.portal.get(), "collective.volto.enhancedlinks") # add behavior to modulo @@ -1597,22 +1616,3 @@ def to_7030(context): if i % 100 == 0: logger.info("Progress: {}/{}".format(i, tot)) brain.getObject().reindexObject(idxs=["enhanced_links_enabled"]) - - -def add_canale_digitale_link_index(context): - update_catalog(context) - update_registry(context) - brains = api.content.find(portal_type="Servizio") - logger.info(f"Found {len(brains)} Servizio content type to reindex") - for brain in brains: - service = brain.getObject() - service.reindexObject(idxs=["canale_digitale_link"]) - logger.info(f"Reindexed {service.absolute_url()}") - logger.info("End of update, added index canale_digitale_link") - - -def to_7031(context): - portal_types = api.portal.get_tool(name="portal_types") - for ptype in ["News Item"]: - portal_types[ptype].default_view = "view" - portal_types[ptype].view_methods = ["view"] From 759ec2fded6df6e51a66fa16f88596de1088e80c Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Mon, 4 Mar 2024 14:19:32 +0100 Subject: [PATCH 8/8] fix method name --- src/design/plone/contenttypes/upgrades/upgrades.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/design/plone/contenttypes/upgrades/upgrades.py b/src/design/plone/contenttypes/upgrades/upgrades.py index 81d655d6..35a48cfa 100644 --- a/src/design/plone/contenttypes/upgrades/upgrades.py +++ b/src/design/plone/contenttypes/upgrades/upgrades.py @@ -1598,7 +1598,7 @@ def to_7031(context): portal_types[ptype].view_methods = ["view"] -def to_7030(context): +def to_7040(context): installOrReinstallProduct(api.portal.get(), "collective.volto.enhancedlinks") # add behavior to modulo portal_types = api.portal.get_tool(name="portal_types")