diff --git a/CHANGES.rst b/CHANGES.rst index bc479736..f3d2b321 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,13 @@ Changelog [cekk] - Improve types test for their schema, required fields, fieldsets. [cekk] +- Add *exclude_from_search* indexer and behavior, and enable for Document and Folder. + [cekk] +- Add custom adapter for IZCatalogCompatibleQuery to force all anonymous @search calls to skip items excluded from search. + [cekk] +- Set *exclude_from_search* to True in all Documents/Folders automatically created in createSubfolders event handler, + and add an upgrade-step that fix already created ones. + [cekk] 6.1.14 (2024-02-20) ------------------- diff --git a/base.cfg b/base.cfg index 20b969de..7a9ad1c4 100644 --- a/base.cfg +++ b/base.cfg @@ -137,6 +137,7 @@ eggs = createcoverage [versions] # Don't use a released version of design.plone.contenttypes design.plone.contenttypes = +plone.restapi = [sources] #collective.volto.blocksfield = git https://github.com/collective/collective.volto.blocksfield.git pushurl=git@github.com:collective/collective.volto.blocksfield.git branch=main diff --git a/src/design/plone/contenttypes/adapters/configure.zcml b/src/design/plone/contenttypes/adapters/configure.zcml index 7e7188fd..ebbd9de7 100644 --- a/src/design/plone/contenttypes/adapters/configure.zcml +++ b/src/design/plone/contenttypes/adapters/configure.zcml @@ -21,4 +21,6 @@ name="text" /> + + diff --git a/src/design/plone/contenttypes/adapters/query.py b/src/design/plone/contenttypes/adapters/query.py new file mode 100644 index 00000000..d3b0e924 --- /dev/null +++ b/src/design/plone/contenttypes/adapters/query.py @@ -0,0 +1,26 @@ +from design.plone.contenttypes.interfaces import IDesignPloneContenttypesLayer +from plone.restapi.interfaces import IZCatalogCompatibleQuery +from plone.restapi.search.query import ZCatalogCompatibleQueryAdapter as BaseAdapter +from zope.component import adapter +from zope.interface import implementer +from zope.interface import Interface +from plone import api + + +@implementer(IZCatalogCompatibleQuery) +@adapter(Interface, IDesignPloneContenttypesLayer) +class ZCatalogCompatibleQueryAdapter(BaseAdapter): + """ """ + + def __call__(self, query): + """ + Do not show excluded from search items when anonymous are performing + some catalog searches + """ + query = super().__call__(query=query) + + if api.user.is_anonymous(): + # For the anonymous user, only content that is not "excluded from the search" is found + query["exclude_from_search"] = False + + return query diff --git a/src/design/plone/contenttypes/behaviors/configure.zcml b/src/design/plone/contenttypes/behaviors/configure.zcml index 4cfdb19b..10d55afa 100644 --- a/src/design/plone/contenttypes/behaviors/configure.zcml +++ b/src/design/plone/contenttypes/behaviors/configure.zcml @@ -314,4 +314,12 @@ marker=".update_note.IUpdateNote" /> + diff --git a/src/design/plone/contenttypes/behaviors/exclude_from_search.py b/src/design/plone/contenttypes/behaviors/exclude_from_search.py new file mode 100644 index 00000000..54d18a8b --- /dev/null +++ b/src/design/plone/contenttypes/behaviors/exclude_from_search.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from design.plone.contenttypes import _ +from plone.autoform.interfaces import IFormFieldProvider +from plone.dexterity.interfaces import IDexterityContent +from plone.supermodel import model +from zope import schema +from zope.component import adapter +from zope.interface import implementer +from zope.interface import provider + + +@provider(IFormFieldProvider) +class IExcludeFromSearch(model.Schema): + """ """ + + exclude_from_search = schema.Bool( + title=_("exclude_from_search_label", default="Escludi dalla ricerca"), + description=_( + "help_exclude_from_search", + default="Se selezionato, questo contenuto non verrĂ  mostrato nelle ricerche del sito per gli utenti anonimi.", + ), + required=False, + default=False, + ) + model.fieldset( + "settings", + fields=["exclude_from_search"], + ) + + +@implementer(IExcludeFromSearch) +@adapter(IDexterityContent) +class ExcludeFromSearch(object): + """ """ + + def __init__(self, context): + self.context = context diff --git a/src/design/plone/contenttypes/events/common.py b/src/design/plone/contenttypes/events/common.py index 51b7c49c..9295226a 100644 --- a/src/design/plone/contenttypes/events/common.py +++ b/src/design/plone/contenttypes/events/common.py @@ -130,11 +130,15 @@ } ], "Servizio": [ - {"id": "modulistica", "title": "Modulistica", "contains": ("File", "Link")}, - {"id": "allegati", "title": "Allegati", "contains": ("File", "Link")}, + { + "id": "modulistica", + "title": "Modulistica", + "allowed_types": ("File", "Link"), + }, + {"id": "allegati", "title": "Allegati", "allowed_types": ("File", "Link")}, ], "UnitaOrganizzativa": [ - {"id": "allegati", "title": "Allegati", "contains": ("File",)}, + {"id": "allegati", "title": "Allegati", "allowed_types": ("File",)}, ], } @@ -160,14 +164,19 @@ def createSubfolders(context, event): return for mapping in subfolders_mapping: if mapping["id"] not in context.keys(): + portal_type = mapping.get("type", "Document") child = api.content.create( container=context, - type=mapping.get("type", "Document"), + type=portal_type, title=mapping["title"], id=mapping["id"], ) - create_default_blocks(context=child) + if portal_type == "Document": + create_default_blocks(context=child) + if portal_type in ["Folder", "Document"]: + child.exclude_from_search = True + child.reindexObject(idxs=["exclude_from_search"]) # select constraints if mapping.get("allowed_types", ()): constraintsChild = ISelectableConstrainTypes(child) diff --git a/src/design/plone/contenttypes/indexers/common.py b/src/design/plone/contenttypes/indexers/common.py index bf23ab58..802c76d2 100644 --- a/src/design/plone/contenttypes/indexers/common.py +++ b/src/design/plone/contenttypes/indexers/common.py @@ -35,3 +35,8 @@ def parent(context): "UID": obj_parent.UID(), "@id": obj_parent.absolute_url(), } + + +@indexer(IDexterityContent) +def exclude_from_search(context): + return getattr(context.aq_base, "exclude_from_search", False) diff --git a/src/design/plone/contenttypes/indexers/configure.zcml b/src/design/plone/contenttypes/indexers/configure.zcml index bcab069a..76f35000 100644 --- a/src/design/plone/contenttypes/indexers/configure.zcml +++ b/src/design/plone/contenttypes/indexers/configure.zcml @@ -73,6 +73,10 @@ factory=".punto_di_contatto.PuntoDiContattoMoreTextToIndex" name="IPuntoDiContatto" /> + diff --git a/src/design/plone/contenttypes/profiles/default/catalog.xml b/src/design/plone/contenttypes/profiles/default/catalog.xml index 4e481d5e..a37b9a79 100644 --- a/src/design/plone/contenttypes/profiles/default/catalog.xml +++ b/src/design/plone/contenttypes/profiles/default/catalog.xml @@ -55,6 +55,9 @@ + + + diff --git a/src/design/plone/contenttypes/profiles/default/metadata.xml b/src/design/plone/contenttypes/profiles/default/metadata.xml index a1d80769..f6ba447d 100644 --- a/src/design/plone/contenttypes/profiles/default/metadata.xml +++ b/src/design/plone/contenttypes/profiles/default/metadata.xml @@ -1,6 +1,6 @@ - 7100 + 7200 profile-redturtle.bandi:default profile-collective.venue:default diff --git a/src/design/plone/contenttypes/profiles/default/types/Document.xml b/src/design/plone/contenttypes/profiles/default/types/Document.xml index 30e528d3..a4b27027 100644 --- a/src/design/plone/contenttypes/profiles/default/types/Document.xml +++ b/src/design/plone/contenttypes/profiles/default/types/Document.xml @@ -17,6 +17,7 @@ + diff --git a/src/design/plone/contenttypes/profiles/default/types/Folder.xml b/src/design/plone/contenttypes/profiles/default/types/Folder.xml new file mode 100644 index 00000000..2ea54936 --- /dev/null +++ b/src/design/plone/contenttypes/profiles/default/types/Folder.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/src/design/plone/contenttypes/tests/test_behavior_exclude_from_search.py b/src/design/plone/contenttypes/tests/test_behavior_exclude_from_search.py new file mode 100644 index 00000000..f07296d9 --- /dev/null +++ b/src/design/plone/contenttypes/tests/test_behavior_exclude_from_search.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +from design.plone.contenttypes.testing import ( + DESIGN_PLONE_CONTENTTYPES_API_FUNCTIONAL_TESTING, +) + +from plone import api +from plone.app.testing import setRoles +from plone.app.testing import SITE_OWNER_NAME +from plone.app.testing import SITE_OWNER_PASSWORD +from plone.app.testing import TEST_USER_ID +from plone.app.testing.helpers import logout +from plone.indexer.interfaces import IIndexableObject +from plone.restapi.interfaces import IZCatalogCompatibleQuery +from plone.restapi.testing import RelativeSession +from transaction import commit +from zope.component import getMultiAdapter +from zope.component import queryMultiAdapter +import unittest + + +class ExcludeFromSearchFunctionalTest(unittest.TestCase): + layer = DESIGN_PLONE_CONTENTTYPES_API_FUNCTIONAL_TESTING + maxDiff = None + + def setUp(self): + self.portal = self.layer["portal"] + self.request = self.layer["request"] + self.portal_url = self.portal.absolute_url() + self.catalog = api.portal.get_tool("portal_catalog") + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + + api.user.create( + email="foo@example.com", + username="foo", + password="secret!!!", + ) + + self.news = api.content.create( + container=self.portal, + type="News Item", + title="Test News", + ) + + self.document = api.content.create( + container=self.portal, + type="Document", + title="Test Document", + ) + + api.content.transition(obj=self.news, transition="publish") + api.content.transition(obj=self.news["multimedia"], transition="publish") + api.content.transition(obj=self.document, transition="publish") + + commit() + + self.api_session = RelativeSession(self.portal_url) + self.api_session.headers.update({"Accept": "application/json"}) + self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) + + self.api_session_foo = RelativeSession(self.portal_url) + self.api_session_foo.headers.update({"Accept": "application/json"}) + self.api_session_foo.auth = ("foo", "secret!!!") + + self.api_session_anon = RelativeSession(self.portal_url) + self.api_session_anon.headers.update({"Accept": "application/json"}) + + def tearDown(self): + self.api_session.close() + self.api_session_anon.close() + + def test_exclude_from_search_indexer_for_item_without_behavior(self): + """ + news item does not have the behavior, so it has False by default + """ + self.assertRaises(AttributeError, getattr, self.news, "exclude_from_search") + adapter = queryMultiAdapter((self.news, self.catalog), IIndexableObject) + self.assertFalse(adapter.exclude_from_search) + + def test_exclude_from_search_indexer_for_item_with_behavior_enabled(self): + """ """ + self.assertFalse(self.document.exclude_from_search) + adapter = queryMultiAdapter((self.document, self.catalog), IIndexableObject) + self.assertFalse(adapter.exclude_from_search) + + def test_exclude_from_search_indexer_for_item_with_behavior_enabled_and_set(self): + """ """ + self.assertTrue(self.news["multimedia"].exclude_from_search) + adapter = queryMultiAdapter( + (self.news["multimedia"], self.catalog), IIndexableObject + ) + self.assertTrue(adapter.exclude_from_search) + + def test_adapter_do_not_append_anything_to_query_for_auth_users(self): + catalog_compatible_query = getMultiAdapter( + (self.portal, self.request), IZCatalogCompatibleQuery + )({}) + self.assertEqual({}, catalog_compatible_query) + + def test_adapter_append_exclude_from_search_to_query_for_anon_users(self): + logout() + catalog_compatible_query = getMultiAdapter( + (self.portal, self.request), IZCatalogCompatibleQuery + )({}) + self.assertEqual(catalog_compatible_query, {"exclude_from_search": False}) + + def test_search_return_excluded_contents_for_logged_users(self): + """ """ + resp = self.api_session.get( + "/@search", params={"SearchableText": "multimedia"} + ).json() + self.assertEqual(resp["items_total"], 1) + + resp = self.api_session_foo.get( + "/@search", params={"SearchableText": "multimedia"} + ).json() + self.assertEqual(resp["items_total"], 1) + + def test_search_do_not_return_excluded_contents_for_anon_users(self): + """ """ + resp = self.api_session_anon.get( + "/@search", params={"SearchableText": "multimedia"} + ).json() + self.assertEqual(resp["items_total"], 0) diff --git a/src/design/plone/contenttypes/tests/test_ct_document.py b/src/design/plone/contenttypes/tests/test_ct_document.py index 3262761c..814e1c18 100644 --- a/src/design/plone/contenttypes/tests/test_ct_document.py +++ b/src/design/plone/contenttypes/tests/test_ct_document.py @@ -50,6 +50,7 @@ def test_behaviors_enabled_for_document(self): "design.plone.contenttypes.behavior.show_modified", "kitconcept.seo", "plone.constraintypes", + "design.plone.contenttypes.behavior.exclude_from_search", "plone.leadimage", "volto.preview_image", ), @@ -128,6 +129,7 @@ def test_document_fields_settings_fieldset(self): "id", "versioning_enabled", "show_modified", + "exclude_from_search", "changeNote", ], ) diff --git a/src/design/plone/contenttypes/tests/test_ct_folder.py b/src/design/plone/contenttypes/tests/test_ct_folder.py new file mode 100644 index 00000000..2c71fed0 --- /dev/null +++ b/src/design/plone/contenttypes/tests/test_ct_folder.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +from design.plone.contenttypes.testing import ( + DESIGN_PLONE_CONTENTTYPES_API_FUNCTIONAL_TESTING, +) +from plone import api +from plone.app.testing import setRoles +from plone.app.testing import TEST_USER_ID +from plone.app.testing import SITE_OWNER_NAME +from plone.app.testing import SITE_OWNER_PASSWORD +from plone.restapi.testing import RelativeSession + +import unittest + + +class TestFolderSchema(unittest.TestCase): + layer = DESIGN_PLONE_CONTENTTYPES_API_FUNCTIONAL_TESTING + + def setUp(self): + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.request = self.layer["request"] + self.portal_url = self.portal.absolute_url() + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + + self.api_session = RelativeSession(self.portal_url) + self.api_session.headers.update({"Accept": "application/json"}) + self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) + + def tearDown(self): + self.api_session.close() + + def test_behaviors_enabled_for_folder(self): + portal_types = api.portal.get_tool(name="portal_types") + self.assertEqual( + portal_types["Folder"].behaviors, + ( + "plone.dublincore", + "plone.namefromtitle", + "plone.allowdiscussion", + "plone.excludefromnavigation", + "plone.shortname", + "plone.constraintypes", + "plone.relateditems", + "plone.nextprevioustoggle", + "design.plone.contenttypes.behavior.exclude_from_search", + ), + ) diff --git a/src/design/plone/contenttypes/tests/test_substructure_creation.py b/src/design/plone/contenttypes/tests/test_substructure_creation.py new file mode 100644 index 00000000..991bc753 --- /dev/null +++ b/src/design/plone/contenttypes/tests/test_substructure_creation.py @@ -0,0 +1,369 @@ +# -*- coding: utf-8 -*- +from design.plone.contenttypes.testing import ( + DESIGN_PLONE_CONTENTTYPES_FUNCTIONAL_TESTING, +) +from plone import api +from plone.app.testing import setRoles +from plone.app.testing import TEST_USER_ID + +import unittest + + +class TestEventCreation(unittest.TestCase): + layer = DESIGN_PLONE_CONTENTTYPES_FUNCTIONAL_TESTING + + def setUp(self): + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.request = self.layer["request"] + self.portal_url = self.portal.absolute_url() + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + + def test_bando_substructure_created(self): + """ + Should have: + - documenti + - comunicazioni + - esiti + """ + item = api.content.create( + container=self.portal, + type="Bando", + title="Test Bando", + ) + + self.assertEqual( + list(item.keys()), + ["documenti", "comunicazioni", "esiti"], + ) + + self.assertEqual(item["documenti"].portal_type, "Bando Folder Deepening") + self.assertEqual(api.content.get_state(item["documenti"]), "private") + + self.assertEqual(item["comunicazioni"].portal_type, "Bando Folder Deepening") + self.assertEqual(api.content.get_state(item["comunicazioni"]), "private") + + self.assertEqual(item["esiti"].portal_type, "Bando Folder Deepening") + self.assertEqual(api.content.get_state(item["esiti"]), "private") + + def test_documento_substructure_created(self): + """ + Should have: + - multimedia + """ + item = api.content.create( + container=self.portal, + type="Documento", + title="Test", + ) + + self.assertEqual( + list(item.keys()), + ["multimedia"], + ) + + self.assertEqual(item["multimedia"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["multimedia"]), "private") + self.assertEqual(item["multimedia"].constrain_types_mode, 1) + self.assertEqual( + item["multimedia"].locally_allowed_types, + ("Image",), + ) + self.assertTrue(item["multimedia"].exclude_from_search) + + def test_event_substructure_created(self): + """ + Should have: + - immagini + - video + - sponsor_evento + - documenti + """ + item = api.content.create( + container=self.portal, + type="Event", + title="Test", + ) + + self.assertEqual( + list(item.keys()), + ["immagini", "video", "sponsor_evento", "documenti"], + ) + + self.assertEqual(item["immagini"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["immagini"]), "published") + self.assertEqual(item["immagini"].constrain_types_mode, 1) + self.assertEqual( + item["immagini"].locally_allowed_types, + ("Image", "Link"), + ) + self.assertTrue(item["immagini"].exclude_from_search) + + self.assertEqual(item["video"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["video"]), "published") + self.assertEqual(item["video"].constrain_types_mode, 1) + self.assertEqual( + item["video"].locally_allowed_types, + ("Link",), + ) + self.assertTrue(item["video"].exclude_from_search) + + self.assertEqual(item["sponsor_evento"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["sponsor_evento"]), "published") + self.assertEqual(item["sponsor_evento"].constrain_types_mode, 1) + self.assertEqual( + item["sponsor_evento"].locally_allowed_types, + ("Link",), + ) + self.assertTrue(item["sponsor_evento"].exclude_from_search) + + self.assertEqual(item["documenti"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["documenti"]), "published") + self.assertEqual(item["documenti"].constrain_types_mode, 1) + self.assertEqual(item["documenti"].locally_allowed_types, ("File",)) + self.assertTrue(item["documenti"].exclude_from_search) + + def test_incarico_substructure_created(self): + """ + Should have: + - compensi-file + - importi-di-viaggio-e-o-servizi + """ + item = api.content.create( + container=self.portal, + type="Incarico", + title="Test", + ) + + self.assertEqual( + list(item.keys()), + ["compensi-file", "importi-di-viaggio-e-o-servizi"], + ) + + self.assertEqual(item["compensi-file"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["compensi-file"]), "private") + self.assertTrue(item["compensi-file"].exclude_from_search) + + self.assertEqual(item["importi-di-viaggio-e-o-servizi"].portal_type, "Document") + self.assertEqual( + api.content.get_state(item["importi-di-viaggio-e-o-servizi"]), "private" + ) + self.assertTrue(item["importi-di-viaggio-e-o-servizi"].exclude_from_search) + + def test_news_substructure_created(self): + """ + Should have: + - multimedia + - documenti allegati + """ + item = api.content.create( + container=self.portal, + type="News Item", + title="Test News", + ) + + self.assertEqual( + list(item.keys()), + ["multimedia", "documenti-allegati"], + ) + + self.assertEqual(item["multimedia"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["multimedia"]), "private") + self.assertEqual(item["multimedia"].constrain_types_mode, 1) + self.assertEqual( + item["multimedia"].locally_allowed_types, + ("Image", "Link"), + ) + self.assertTrue(item["multimedia"].exclude_from_search) + + self.assertEqual(item["documenti-allegati"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["documenti-allegati"]), "private") + self.assertEqual(item["documenti-allegati"].constrain_types_mode, 1) + self.assertEqual( + item["documenti-allegati"].locally_allowed_types, + ("File", "Image"), + ) + self.assertTrue(item["multimedia"].exclude_from_search) + + def test_venue_substructure_created(self): + """ + Should have: + - multimedia + """ + item = api.content.create( + container=self.portal, + type="Venue", + title="Test", + ) + + self.assertEqual( + list(item.keys()), + ["multimedia"], + ) + + self.assertEqual(item["multimedia"].portal_type, "Folder") + self.assertEqual(api.content.get_state(item["multimedia"]), "published") + self.assertEqual(item["multimedia"].constrain_types_mode, 1) + self.assertEqual( + item["multimedia"].locally_allowed_types, + ("Image", "Link"), + ) + self.assertTrue(item["multimedia"].exclude_from_search) + + def test_persona_substructure_created(self): + """ + Should have: + - foto-e-attivita-politica + - curriculum-vitae + - situazione-patrimoniale + - dichiarazione-dei-redditi + - spese-elettorali + - spese-elettorali + - variazione-situazione-patrimoniale" "altre-cariche + - incarichi + """ + item = api.content.create( + container=self.portal, + type="Persona", + title="Test", + ) + + self.assertEqual( + list(item.keys()), + [ + "foto-e-attivita-politica", + "curriculum-vitae", + "situazione-patrimoniale", + "dichiarazione-dei-redditi", + "spese-elettorali", + "variazione-situazione-patrimoniale", + "altre-cariche", + "incarichi", + ], + ) + + self.assertEqual(item["foto-e-attivita-politica"].portal_type, "Document") + self.assertEqual( + api.content.get_state(item["foto-e-attivita-politica"]), "private" + ) + self.assertEqual(item["foto-e-attivita-politica"].constrain_types_mode, 1) + self.assertEqual( + item["foto-e-attivita-politica"].locally_allowed_types, + ("Image",), + ) + self.assertTrue(item["foto-e-attivita-politica"].exclude_from_search) + + self.assertEqual(item["curriculum-vitae"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["curriculum-vitae"]), "private") + self.assertEqual(item["curriculum-vitae"].constrain_types_mode, 1) + self.assertEqual(item["curriculum-vitae"].locally_allowed_types, ("File",)) + self.assertTrue(item["curriculum-vitae"].exclude_from_search) + + self.assertEqual(item["situazione-patrimoniale"].portal_type, "Document") + self.assertEqual( + api.content.get_state(item["situazione-patrimoniale"]), "private" + ) + self.assertEqual(item["situazione-patrimoniale"].constrain_types_mode, 1) + self.assertEqual( + item["situazione-patrimoniale"].locally_allowed_types, ("File",) + ) + self.assertTrue(item["situazione-patrimoniale"].exclude_from_search) + + self.assertEqual(item["dichiarazione-dei-redditi"].portal_type, "Document") + self.assertEqual( + api.content.get_state(item["dichiarazione-dei-redditi"]), "private" + ) + self.assertEqual(item["dichiarazione-dei-redditi"].constrain_types_mode, 1) + self.assertEqual( + item["dichiarazione-dei-redditi"].locally_allowed_types, ("File",) + ) + self.assertTrue(item["dichiarazione-dei-redditi"].exclude_from_search) + + self.assertEqual(item["spese-elettorali"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["spese-elettorali"]), "private") + self.assertEqual(item["spese-elettorali"].constrain_types_mode, 1) + self.assertEqual(item["spese-elettorali"].locally_allowed_types, ("File",)) + self.assertTrue(item["spese-elettorali"].exclude_from_search) + + self.assertEqual(item["curriculum-vitae"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["curriculum-vitae"]), "private") + self.assertEqual(item["curriculum-vitae"].constrain_types_mode, 1) + self.assertEqual(item["curriculum-vitae"].locally_allowed_types, ("File",)) + self.assertTrue(item["curriculum-vitae"].exclude_from_search) + + self.assertEqual( + item["variazione-situazione-patrimoniale"].portal_type, "Document" + ) + self.assertEqual( + api.content.get_state(item["variazione-situazione-patrimoniale"]), "private" + ) + self.assertEqual( + item["variazione-situazione-patrimoniale"].constrain_types_mode, 1 + ) + self.assertEqual( + item["variazione-situazione-patrimoniale"].locally_allowed_types, ("File",) + ) + self.assertTrue(item["variazione-situazione-patrimoniale"].exclude_from_search) + + self.assertEqual(item["altre-cariche"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["altre-cariche"]), "private") + self.assertEqual(item["altre-cariche"].constrain_types_mode, 1) + self.assertEqual(item["altre-cariche"].locally_allowed_types, ("File",)) + self.assertTrue(item["altre-cariche"].exclude_from_search) + + self.assertEqual(item["incarichi"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["incarichi"]), "private") + self.assertEqual(item["incarichi"].constrain_types_mode, 1) + self.assertEqual(item["incarichi"].locally_allowed_types, ("Incarico",)) + self.assertTrue(item["incarichi"].exclude_from_search) + + def test_servizio_substructure_created(self): + """ + Should have: + - modulistica + - allegati + """ + item = api.content.create( + container=self.portal, + type="Servizio", + title="Test", + ) + + self.assertEqual( + list(item.keys()), + ["modulistica", "allegati"], + ) + + self.assertEqual(item["modulistica"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["modulistica"]), "private") + self.assertEqual(item["modulistica"].constrain_types_mode, 1) + self.assertEqual(item["modulistica"].locally_allowed_types, ("File", "Link")) + self.assertTrue(item["modulistica"].exclude_from_search) + + self.assertEqual(item["allegati"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["allegati"]), "private") + self.assertEqual(item["allegati"].constrain_types_mode, 1) + self.assertEqual(item["allegati"].locally_allowed_types, ("File", "Link")) + self.assertTrue(item["allegati"].exclude_from_search) + + def test_uo_substructure_created(self): + """ + Should have: + - allegati + """ + item = api.content.create( + container=self.portal, + type="UnitaOrganizzativa", + title="Test", + ) + + self.assertEqual( + list(item.keys()), + ["allegati"], + ) + + self.assertEqual(item["allegati"].portal_type, "Document") + self.assertEqual(api.content.get_state(item["allegati"]), "private") + self.assertEqual(item["allegati"].constrain_types_mode, 1) + self.assertEqual(item["allegati"].locally_allowed_types, ("File",)) + self.assertTrue(item["allegati"].exclude_from_search) diff --git a/src/design/plone/contenttypes/upgrades/configure.zcml b/src/design/plone/contenttypes/upgrades/configure.zcml index 53231884..7d4fbff9 100644 --- a/src/design/plone/contenttypes/upgrades/configure.zcml +++ b/src/design/plone/contenttypes/upgrades/configure.zcml @@ -838,4 +838,14 @@ handler=".upgrades.to_7100" /> + + + diff --git a/src/design/plone/contenttypes/upgrades/upgrades.py b/src/design/plone/contenttypes/upgrades/upgrades.py index af7bbfa6..0702a58b 100644 --- a/src/design/plone/contenttypes/upgrades/upgrades.py +++ b/src/design/plone/contenttypes/upgrades/upgrades.py @@ -22,6 +22,7 @@ from zope.intid.interfaces import IIntIds from zope.lifecycleevent import ObjectModifiedEvent from zope.schema import getFields +from design.plone.contenttypes.events.common import SUBFOLDERS_MAPPING import json import logging @@ -1616,3 +1617,66 @@ def to_7100(context): if i % 100 == 0: logger.info("Progress: {}/{}".format(i, tot)) brain.getObject().reindexObject(idxs=["enhanced_links_enabled"]) + + +def to_7200(context): + update_catalog(context) + # add behavior to Document and Folder + bhv = "design.plone.contenttypes.behavior.exclude_from_search" + portal_types = api.portal.get_tool(name="portal_types") + for ptype in ["Document", "Folder"]: + behaviors = [x for x in portal_types[ptype].behaviors] + if bhv not in behaviors: + behaviors.append(bhv) + portal_types[ptype].behaviors = tuple(behaviors) + + # set True to all of already created children + # update index/metadata + brains = api.content.find(portal_type=[x for x in SUBFOLDERS_MAPPING.keys()]) + tot = len(brains) + i = 0 + for brain in brains: + i += 1 + if i % 100 == 0: + logger.info("Progress: {}/{}".format(i, tot)) + container = brain.getObject() + mappings = SUBFOLDERS_MAPPING.get(container.portal_type, []) + persona_old_mapping = [ + { + "id": "foto-e-attivita-politica", + }, + {"id": "curriculum-vitae"}, + {"id": "compensi"}, + { + "id": "importi-di-viaggio-e-o-servizi", + }, + { + "id": "situazione-patrimoniale", + }, + { + "id": "dichiarazione-dei-redditi", + }, + { + "id": "spese-elettorali", + }, + { + "id": "variazione-situazione-patrimoniale", + }, + { + "id": "altre-cariche", + }, + ] + if container.portal_type == "Persona": + # cleanup also some old-style (v2) folders + mappings.extend(persona_old_mapping) + + for mapping in mappings: + child = container.get(mapping["id"], None) + if not child: + continue + if child.portal_type not in ["Folder", "Document"]: + continue + child.exclude_from_search = True + + catalog = api.portal.get_tool(name="portal_catalog") + catalog.manage_reindexIndex(ids=["exclude_from_search"])