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"])