Added collections search
authorThierry Florac <tflorac@ulthar.net>
Fri, 26 Jun 2020 12:54:13 +0200
changeset 1389 1485db1e2b5e
parent 1388 8c757af2fc50
child 1390 4839e7691132
Added collections search
src/pyams_content/component/theme/__init__.py
src/pyams_content/component/theme/interfaces.py
src/pyams_content/component/theme/zmi/__init__.py
src/pyams_content/component/theme/zmi/manager.py
src/pyams_content/features/search/interfaces.py
src/pyams_content/features/search/manager.py
src/pyams_content/features/search/portlet/__init__.py
src/pyams_content/features/search/zmi/manager.py
src/pyams_content/generations/__init__.py
src/pyams_content/generations/evolve2.py
src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.mo
src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po
src/pyams_content/locales/pyams_content.pot
src/pyams_content/root/__init__.py
src/pyams_content/shared/resource/manager.py
src/pyams_content/shared/view/interfaces.py
src/pyams_content/shared/view/theme.py
--- a/src/pyams_content/component/theme/__init__.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/component/theme/__init__.py	Fri Jun 26 12:54:13 2020 +0200
@@ -10,21 +10,23 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
 from persistent import Persistent
 from zope.container.contained import Contained
 from zope.schema.fieldproperty import FieldProperty
 
-from pyams_content.component.theme.interfaces import IThemesManagerTarget, IThemesManager, THEMES_MANAGER_KEY, \
-    IThemesTarget, IThemesInfo, THEMES_INFO_KEY, ITagsManager, ITagsManagerTarget, TAGS_MANAGER_KEY, ITagsInfo, \
-    ITagsTarget, TAGS_INFO_KEY, ICollectionsManager, ICollectionsManagerTarget, COLLECTIONS_MANAGER_KEY, \
-    ICollectionsInfo, ICollectionsTarget, COLLECTIONS_INFO_KEY
+from pyams_content.component.theme.interfaces import COLLECTIONS_INFO_KEY, \
+    COLLECTIONS_MANAGER_KEY, ICollectionsInfo, ICollectionsManager, ICollectionsManagerTarget, \
+    ICollectionsTarget, ITagsInfo, ITagsManager, ITagsManagerTarget, ITagsTarget, IThemesInfo, \
+    IThemesManager, IThemesManagerTarget, IThemesTarget, TAGS_INFO_KEY, TAGS_MANAGER_KEY, \
+    THEMES_INFO_KEY, THEMES_MANAGER_KEY
 from pyams_content.features.checker import BaseContentChecker
-from pyams_content.features.checker.interfaces import IContentChecker, ERROR_VALUE
+from pyams_content.features.checker.interfaces import ERROR_VALUE, IContentChecker
 from pyams_utils.adapter import adapter_config, get_annotation_adapter
 from pyams_utils.factory import factory_config
 
+
+__docformat__ = 'restructuredtext'
+
 from pyams_content import _
 
 
@@ -39,9 +41,6 @@
     thesaurus_name = FieldProperty(ITagsManager['thesaurus_name'])
     extract_name = FieldProperty(ITagsManager['extract_name'])
 
-    enable_tags_search = FieldProperty(ITagsManager['enable_tags_search'])
-    tags_search_target = FieldProperty(ITagsManager['tags_search_target'])
-
     enable_glossary = FieldProperty(ITagsManager['enable_glossary'])
     glossary_thesaurus_name = FieldProperty(ITagsManager['glossary_thesaurus_name'])
 
@@ -79,8 +78,9 @@
         translate = request.localizer.translate
         tags = ITagsInfo(self.context)
         if not tags.tags:
-            output.append(translate(ERROR_VALUE).format(field=translate(ITagsInfo['tags'].title),
-                                                        message=translate(_("no defined tag"))))
+            output.append(translate(ERROR_VALUE).format(
+                field=translate(ITagsInfo['tags'].title),
+                message=translate(_("no defined tag"))))
         return output
 
 
@@ -129,8 +129,9 @@
         translate = request.localizer.translate
         themes = IThemesInfo(self.context)
         if not themes.themes:
-            output.append(translate(ERROR_VALUE).format(field=translate(IThemesInfo['themes'].title),
-                                                        message=translate(_("no defined theme"))))
+            output.append(translate(ERROR_VALUE).format(
+                field=translate(IThemesInfo['themes'].title),
+                message=translate(_("no defined theme"))))
         return output
 
 
@@ -179,6 +180,7 @@
         translate = request.localizer.translate
         collections = ICollectionsInfo(self.context)
         if not collections.collections:
-            output.append(translate(ERROR_VALUE).format(field=translate(ICollectionsInfo['collections'].title),
-                                                        message=translate(_("no defined collection"))))
+            output.append(translate(ERROR_VALUE).format(
+                field=translate(ICollectionsInfo['collections'].title),
+                message=translate(_("no defined collection"))))
         return output
--- a/src/pyams_content/component/theme/interfaces.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/component/theme/interfaces.py	Fri Jun 26 12:54:13 2020 +0200
@@ -12,15 +12,14 @@
 
 __docformat__ = 'restructuredtext'
 
-from zope.interface import Interface, invariant, Invalid
+from zope.interface import Interface, Invalid, invariant
 from zope.schema import Bool, Choice
 
-from pyams_sequence.schema import InternalReferenceField
-from pyams_thesaurus.interfaces.thesaurus import IThesaurusContextManager, IThesaurusContextManagerTarget
+from pyams_content import _
+from pyams_thesaurus.interfaces.thesaurus import IThesaurusContextManager, \
+    IThesaurusContextManagerTarget
 from pyams_thesaurus.schema import ThesaurusTermsListField
 
-from pyams_content import _
-
 
 #
 # Tags management
@@ -33,21 +32,6 @@
 class ITagsManager(IThesaurusContextManager):
     """Tags manager interface"""
 
-    enable_tags_search = Bool(title=_("Enable search by tag?"),
-                              description=_("If 'yes', displayed tags will lead to a search engine "
-                                            "displaying contents matching given tag"),
-                              required=True,
-                              default=False)
-
-    tags_search_target = InternalReferenceField(title=_("Tags search target"),
-                                                description=_("Site or folder where tags search is displayed"),
-                                                required=False)
-
-    @invariant
-    def check_search_target(self):
-        if self.enable_tags_search and not self.tags_search_target:
-            raise Invalid(_("You must specify search target when activating search by tags!"))
-
     enable_glossary = Bool(title=_("Enable glossary?"),
                            description=_(""),
                            required=True,
--- a/src/pyams_content/component/theme/zmi/__init__.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/component/theme/zmi/__init__.py	Fri Jun 26 12:54:13 2020 +0200
@@ -10,35 +10,31 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
+from z3c.form import field
+from zope.interface import implementer
 
-# import interfaces
-from pyams_content.component.theme import ITagsTarget, ITagsInfo, ITagsManager, IThemesTarget, IThemesInfo, \
-    IThemesManagerTarget, IThemesManager, ICollectionsTarget, ICollectionsInfo, ICollectionsManager, \
-    ICollectionsManagerTarget
+from pyams_content.component.theme import ICollectionsInfo, ICollectionsManager, \
+    ICollectionsManagerTarget, ICollectionsTarget, ITagsInfo, ITagsManager, ITagsTarget, \
+    IThemesInfo, IThemesManager, IThemesManagerTarget, IThemesTarget
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
+from pyams_content.shared.common.zmi import WfSharedContentHeaderAdapter
+from pyams_form.form import AJAXEditForm, EditForm, ajax_config
 from pyams_form.interfaces.form import IWidgetForm
-from pyams_skin.interfaces import IInnerPage, IPageHeader, IDialog
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.interfaces import IDialog, IInnerPage, IPageHeader
 from pyams_skin.layer import IPyAMSLayer
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_thesaurus.zmi.widget import ThesaurusTermsTreeFieldWidget
+from pyams_utils.adapter import adapter_config
 from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+from pyams_utils.traversing import get_parent
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.form import AdminDialogEditForm, AdminEditForm
 from pyams_zmi.interfaces.menu import IPropertiesMenu
 from pyams_zmi.layer import IAdminLayer
 
-# import packages
-from pyams_content.shared.common.zmi import WfSharedContentHeaderAdapter
-from pyams_form.form import ajax_config, AJAXEditForm, EditForm
-from pyams_pagelet.pagelet import pagelet_config
-from pyams_skin.viewlet.menu import MenuItem
-from pyams_thesaurus.zmi.widget import ThesaurusTermsTreeFieldWidget
-from pyams_utils.adapter import adapter_config
-from pyams_utils.traversing import get_parent
-from pyams_viewlet.viewlet import viewlet_config
-from pyams_zmi.form import AdminEditForm, AdminDialogEditForm
-from z3c.form import field
-from zope.interface import implementer
+
+__docformat__ = 'restructuredtext'
 
 from pyams_content import _
 
@@ -57,8 +53,10 @@
     url = '#tags.html'
 
 
-@pagelet_config(name='tags.html', context=ITagsTarget, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
-@ajax_config(name='tags.json', context=ITagsTarget, layer=IPyAMSLayer, permission=MANAGE_CONTENT_PERMISSION)
+@pagelet_config(name='tags.html', context=ITagsTarget, layer=IPyAMSLayer,
+                permission=VIEW_SYSTEM_PERMISSION)
+@ajax_config(name='tags.json', context=ITagsTarget, layer=IPyAMSLayer,
+             permission=MANAGE_CONTENT_PERMISSION)
 @implementer(IWidgetForm, IInnerPage)
 class TagsEditForm(AdminEditForm):
     """Tags edit form"""
@@ -177,24 +175,17 @@
     fields = field.Fields(ICollectionsInfo)
     fields['collections'].widgetFactory = ThesaurusTermsTreeFieldWidget
 
-    def __init__(self, context, request):
-        super(CollectionsEditForm, self).__init__(context, request)
-        target = get_parent(self.context, ICollectionsManagerTarget)
-        manager = ICollectionsManager(target)
-        self.thesaurus_name = manager.thesaurus_name
-        self.extract_name = manager.extract_name
-
     def updateWidgets(self, prefix=None):
         super(CollectionsEditForm, self).updateWidgets(prefix)
         if 'collections' in self.widgets:
             widget = self.widgets['collections']
-            target = get_parent(self.context, ICollectionsManagerTarget)
-            manager = ICollectionsManager(target)
+            manager = ICollectionsManager(self.request.root)
             widget.thesaurus_name = manager.thesaurus_name
             widget.extract_name = manager.extract_name
 
 
-@adapter_config(context=(ICollectionsTarget, IAdminLayer, CollectionsEditForm), provides=IPageHeader)
+@adapter_config(context=(ICollectionsTarget, IAdminLayer, CollectionsEditForm),
+                provides=IPageHeader)
 class CollectionsHeaderAdapter(WfSharedContentHeaderAdapter):
     """Shared content collections header adapter"""
 
--- a/src/pyams_content/component/theme/zmi/manager.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/component/theme/zmi/manager.py	Fri Jun 26 12:54:13 2020 +0200
@@ -17,8 +17,9 @@
 from zope.interface import alsoProvides
 
 from pyams_content import _
-from pyams_content.component.theme.interfaces import ICollectionsManager, ICollectionsManagerTarget, ITagsManager, \
-    ITagsManagerTarget, IThemesManager, IThemesManagerTarget
+from pyams_content.component.theme.interfaces import ICollectionsManager, \
+    ICollectionsManagerTarget, ITagsManager, ITagsManagerTarget, IThemesManager, \
+    IThemesManagerTarget
 from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION, MANAGE_TOOL_PERMISSION
 from pyams_content.zmi import pyams_content
 from pyams_form.form import ajax_config
@@ -50,7 +51,7 @@
 
 
 @pagelet_config(name='tags.html', context=ITagsManagerTarget, layer=IPyAMSLayer,
-                permission=MANAGE_TOOL_PERMISSION)
+                permission=MANAGE_SITE_ROOT_PERMISSION)
 @ajax_config(name='tags.json', context=ITagsManagerTarget, layer=IPyAMSLayer)
 class TagsManagerEditForm(AdminDialogEditForm):
     """Tags manager edit form"""
@@ -61,7 +62,6 @@
     dialog_class = 'modal-large'
 
     fields = field.Fields(ITagsManager)
-    fields['enable_tags_search'].widgetFactory = SingleCheckBoxFieldWidget
     fields['enable_glossary'].widgetFactory = SingleCheckBoxFieldWidget
 
     edit_permission = MANAGE_SITE_ROOT_PERMISSION
@@ -70,7 +70,8 @@
         return ITagsManager(self.context)
 
     def updateWidgets(self, prefix=None):
-        # We have to store thesaurus name if request header to be able to set extract name correctly
+        # We have to store thesaurus name in request header
+        # to be able to set extract name correctly
         # See :ref:`pyams_thesaurus.thesaurus.ThesaurusExtractsVocabulary`
         if self.request.method == 'POST':
             param_name = '{}widgets.thesaurus_name:list'.format(self.prefix)
@@ -88,13 +89,6 @@
         alsoProvides(widget, IObjectData)
 
     def updateGroups(self):
-        self.add_group(NamedWidgetsGroup(self, 'tags_search', self.widgets,
-                                         ('enable_tags_search', 'tags_search_target'),
-                                         legend=_("Enable search by tag"),
-                                         css_class='inner',
-                                         switch=True,
-                                         checkbox_switch=True,
-                                         checkbox_field=ITagsManager['enable_tags_search']))
         self.add_group(NamedWidgetsGroup(self, 'glossary', self.widgets,
                                          ('enable_glossary', 'glossary_thesaurus_name'),
                                          legend=_("Enable glossary"),
@@ -137,7 +131,8 @@
         return IThemesManager(self.context)
 
     def updateWidgets(self, prefix=None):
-        # We have to store thesaurus name if request header to be able to set extract name correctly
+        # We have to store thesaurus name in request header
+        # to be able to set extract name correctly
         # See :ref:`pyams_thesaurus.thesaurus.ThesaurusExtractsVocabulary`
         if self.request.method == 'POST':
             param_name = '{}widgets.thesaurus_name:list'.format(self.prefix)
@@ -159,8 +154,9 @@
 # Collections management view
 #
 
-@viewlet_config(name='collections-manager.menu', context=ICollectionsManagerTarget, layer=IAdminLayer,
-                manager=IPropertiesMenu, permission=MANAGE_TOOL_PERMISSION, weight=42)
+@viewlet_config(name='collections-manager.menu', context=ICollectionsManagerTarget,
+                layer=IAdminLayer, manager=IPropertiesMenu, permission=MANAGE_TOOL_PERMISSION,
+                weight=42)
 class CollectionsManagerMenu(MenuItem):
     """Collections menu"""
 
@@ -171,7 +167,7 @@
 
 
 @pagelet_config(name='collections.html', context=ICollectionsManagerTarget, layer=IPyAMSLayer,
-                permission=MANAGE_TOOL_PERMISSION)
+                permission=MANAGE_SITE_ROOT_PERMISSION)
 @ajax_config(name='collections.json', context=ICollectionsManagerTarget, layer=IPyAMSLayer)
 class CollectionsManagerEditForm(AdminDialogEditForm):
     """Collections manager edit form"""
@@ -179,15 +175,17 @@
     prefix = 'collections_manager.'
 
     legend = _("Selected collections")
+    dialog_class = 'modal-large'
 
     fields = field.Fields(ICollectionsManager)
-    edit_permission = MANAGE_TOOL_PERMISSION
+    edit_permission = MANAGE_SITE_ROOT_PERMISSION
 
     def getContent(self):
         return ICollectionsManager(self.context)
 
     def updateWidgets(self, prefix=None):
-        # We have to store thesaurus name if request header to be able to set extract name correctly
+        # We have to store thesaurus name in request header
+        # to be able to set extract name correctly
         # See :ref:`pyams_thesaurus.thesaurus.ThesaurusExtractsVocabulary`
         if self.request.method == 'POST':
             param_name = '{}widgets.thesaurus_name:list'.format(self.prefix)
--- a/src/pyams_content/features/search/interfaces.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/features/search/interfaces.py	Fri Jun 26 12:54:13 2020 +0200
@@ -10,12 +10,9 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
-from zope.interface import Interface
+from zope.interface import Attribute, Interface, Invalid, invariant
 from zope.schema import Bool, Choice, Set
 
-from pyams_content import _
 from pyams_content.interfaces import GUEST_ROLE, IBaseContent, MANAGER_ROLE
 from pyams_content.shared.common.interfaces import SHARED_CONTENT_TYPES_VOCABULARY
 from pyams_content.shared.common.interfaces.types import ALL_DATA_TYPES_VOCABULARY
@@ -25,19 +22,27 @@
 from pyams_i18n.schema import I18nTextLineField
 from pyams_portal.interfaces import DESIGNER_ROLE
 from pyams_security.schema import PrincipalsSet
-from pyams_sequence.interfaces import IInternalReference, ISequentialIdTarget
+from pyams_sequence.interfaces import IInternalReferencesList, ISequentialIdTarget
 from pyams_sequence.schema import InternalReferenceField
 
 
-class ISearchManagerInfo(IInternalReference):
+__docformat__ = 'restructuredtext'
+
+from pyams_content import _
+
+
+class ISearchManagerInfo(IInternalReferencesList):
     """Search manager interface"""
 
     reference = InternalReferenceField(title=_("Main search engine"),
-                                       description=_("Search folder handling main site search. You can search a "
-                                                     "reference using '+' followed by internal number, of by "
+                                       description=_("Search folder handling main site search. "
+                                                     "You can search a reference using '+' "
+                                                     "followed by internal number, of by "
                                                      "entering text matching content title."),
                                        required=False)
 
+    search_target = Attribute("Search target object")
+
     name = I18nTextLineField(title=_("Search engine name"),
                              description=_("Name given to the search engine"),
                              required=False)
@@ -46,23 +51,64 @@
                                     description=_("Description given to the search engine"),
                                     required=False)
 
+    enable_tags_search = Bool(title=_("Enable search by tag?"),
+                              description=_("If 'yes', displayed tags will lead to a search "
+                                            "engine displaying contents matching given tag"),
+                              required=True,
+                              default=False)
+
+    tags_search_target = InternalReferenceField(title=_("Tags search target"),
+                                                description=_("Site or folder where tags search "
+                                                              "is displayed"),
+                                                required=False)
+
+    tags_target = Attribute("Tags search target object reference")
+
+    @invariant
+    def check_tags_search_target(self):
+        if self.enable_tags_search and not self.tags_search_target:
+            raise Invalid(_("You must specify search target when activating search by tags!"))
+
+    enable_collections_search = Bool(title=_("Enable search by collection?"),
+                                     description=_("If 'yes', displayed collections will lead to "
+                                                   "a search engine displaying contents matching "
+                                                   "given collection"),
+                                     required=True,
+                                     default=False)
+
+    collections_search_target = InternalReferenceField(title=_("Collections search target"),
+                                                       description=_("Site or folder where "
+                                                                     "collections search is "
+                                                                     "displayed"),
+                                                       required=False)
+
+    collections_target = Attribute("Collections search target object reference")
+
+    @invariant
+    def check_collections_search_target(self):
+        if self.enable_collections_search and not self.collections_search_target:
+            raise Invalid(_("You must specify search target when activating search by "
+                            "collections!"))
+
 
 class ISearchFolderRoles(Interface):
     """Search folder roles"""
 
     managers = PrincipalsSet(title=_("Managers"),
-                             description=_("Managers can handle main operations in tool's workflow, like publish "
-                                           "or retire contents"),
+                             description=_("Managers can handle main operations in tool's "
+                                           "workflow, like publish or retire contents"),
                              role_id=MANAGER_ROLE,
                              required=False)
 
     designers = PrincipalsSet(title=_("Designers"),
-                              description=_("Designers are users which are allowed to manage presentation templates"),
+                              description=_("Designers are users which are allowed to manage "
+                                            "presentation templates"),
                               role_id=DESIGNER_ROLE,
                               required=False)
 
     guests = PrincipalsSet(title=_("Guests"),
-                           description=_("Guests are users which are allowed to view contents with restricted access"),
+                           description=_("Guests are users which are allowed to view contents "
+                                         "with restricted access"),
                            role_id=GUEST_ROLE,
                            required=False)
 
@@ -71,21 +117,24 @@
     """Search folder interface"""
 
     order_by = Choice(title=_("Order by"),
-                      description=_("Property to use to sort results; publication date can be different from first "
-                                    "publication date for contents which have been retired and re-published with a "
-                                    "different publication date"),
+                      description=_("Property to use to sort results; publication date can be "
+                                    "different from first publication date for contents which "
+                                    "have been retired and re-published with a different "
+                                    "publication date"),
                       vocabulary=USER_VIEW_ORDER_VOCABULARY,
                       required=False,
                       default=RELEVANCE_ORDER)
 
     visible_in_list = Bool(title=_("Visible in folders list"),
-                           description=_("If 'no', folder will not be displayed into folders list"),
+                           description=_("If 'no', folder will not be displayed into folders "
+                                         "list"),
                            required=True,
                            default=True)
 
     navigation_title = I18nTextLineField(title=_("Navigation title"),
-                                         description=_("Folder's title displayed in navigation pages; "
-                                                       "original title will be used if none is specified"),
+                                         description=_("Folder's title displayed in navigation "
+                                                       "pages; original title will be used if "
+                                                       "none is specified"),
                                          required=False)
 
     selected_content_types = Set(title=_("Selected content types"),
--- a/src/pyams_content/features/search/manager.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/features/search/manager.py	Fri Jun 26 12:54:13 2020 +0200
@@ -16,22 +16,91 @@
 from zope.container.contained import Contained
 from zope.schema.fieldproperty import FieldProperty
 
-from pyams_content.component.links import InternalReferenceMixin
 from pyams_content.features.search.interfaces import ISearchManagerInfo
 from pyams_content.root import ISiteRoot
+from pyams_sequence.reference import get_reference_target
 from pyams_utils.adapter import adapter_config, get_annotation_adapter
 from pyams_utils.factory import factory_config
+from pyams_utils.zodb import volatile_property
+
 
 SEARCH_MANAGER_INFO_KEY = 'pyams_content.search'
 
 
 @factory_config(ISearchManagerInfo)
-class SearchManagerInfo(Persistent, Contained, InternalReferenceMixin):
+class SearchManagerInfo(Persistent, Contained):
     """Search manager persistent info"""
 
+    _reference = FieldProperty(ISearchManagerInfo['reference'])
     name = FieldProperty(ISearchManagerInfo['name'])
     description = FieldProperty(ISearchManagerInfo['description'])
 
+    enable_tags_search = FieldProperty(ISearchManagerInfo['enable_tags_search'])
+    _tags_search_target = FieldProperty(ISearchManagerInfo['tags_search_target'])
+
+    enable_collections_search = FieldProperty(ISearchManagerInfo['enable_collections_search'])
+    _collections_search_target = FieldProperty(ISearchManagerInfo['collections_search_target'])
+
+    # main search target
+
+    @property
+    def reference(self):
+        return self._reference
+
+    @reference.setter
+    def reference(self, value):
+        self._reference = value
+        del self.search_target
+
+    @volatile_property
+    def search_target(self):
+        return get_reference_target(self._reference)
+
+    # tags search target
+
+    @property
+    def tags_search_target(self):
+        return self._tags_search_target
+
+    @tags_search_target.setter
+    def tags_search_target(self, value):
+        self._tags_search_target = value
+        del self.tags_target
+
+    @volatile_property
+    def tags_target(self):
+        if self.enable_tags_search:
+            return get_reference_target(self._tags_search_target)
+        return None
+
+    # collections search target
+
+    @property
+    def collections_search_target(self):
+        return self._collections_search_target
+
+    @collections_search_target.setter
+    def collections_search_target(self, value):
+        self._collections_search_target = value
+        del self.collections_target
+
+    @volatile_property
+    def collections_target(self):
+        if self.enable_collections_search:
+            return get_reference_target(self._collections_search_target)
+        return None
+
+    @property
+    def references(self):
+        return list(filter(lambda x: x is not None, [self.reference,
+                                                     self.tags_search_target,
+                                                     self.collections_search_target]))
+
+    def get_targets(self, state=None):
+        for reference in [self.reference, self.tags_search_target, self.collections_search_target]:
+            if reference is not None:
+                yield get_reference_target(reference, state)
+
 
 @adapter_config(context=ISiteRoot, provides=ISearchManagerInfo)
 def site_root_search_manager_adapter(context):
--- a/src/pyams_content/features/search/portlet/__init__.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/features/search/portlet/__init__.py	Fri Jun 26 12:54:13 2020 +0200
@@ -42,7 +42,9 @@
     @staticmethod
     def has_user_query(request):
         params = request.params
-        return params.get('user_search', '').strip() or params.get('tag', '').strip()
+        return params.get('user_search', '').strip() or \
+            params.get('tag', '').strip() or \
+            params.get('collection', '').strip()
 
     @staticmethod
     def _get_items(request=None, start=0, length=10, limit=None, ignore_cache=False):
--- a/src/pyams_content/features/search/zmi/manager.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/features/search/zmi/manager.py	Fri Jun 26 12:54:13 2020 +0200
@@ -13,11 +13,13 @@
 __docformat__ = 'restructuredtext'
 
 from z3c.form import field
+from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
 
 from pyams_content.features.search.interfaces import ISearchManagerInfo
 from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION
 from pyams_content.root import ISiteRoot
 from pyams_form.form import ajax_config
+from pyams_form.group import NamedWidgetsGroup
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.layer import IPyAMSLayer
 from pyams_skin.viewlet.menu import MenuDivider, MenuItem
@@ -57,8 +59,30 @@
     legend = _("Search engine settings")
     dialog_class = 'modal-large'
 
-    fields = field.Fields(ISearchManagerInfo)
+    fields = field.Fields(ISearchManagerInfo).omit('references')
+    fields['enable_tags_search'].widgetFactory = SingleCheckBoxFieldWidget
+    fields['enable_collections_search'].widgetFactory = SingleCheckBoxFieldWidget
+
     edit_permission = MANAGE_SITE_ROOT_PERMISSION
 
     def getContent(self):
         return ISearchManagerInfo(self.context)
+
+    def updateGroups(self):
+        self.add_group(NamedWidgetsGroup(self, 'tags_search', self.widgets,
+                                         ('enable_tags_search', 'tags_search_target'),
+                                         legend=_("Enable search by tag"),
+                                         css_class='inner',
+                                         switch=True,
+                                         checkbox_switch=True,
+                                         checkbox_field=ISearchManagerInfo['enable_tags_search']))
+        self.add_group(NamedWidgetsGroup(self, 'collections_search', self.widgets,
+                                         ('enable_collections_search',
+                                          'collections_search_target'),
+                                         legend=_("Enable search by collection"),
+                                         css_class='inner',
+                                         switch=True,
+                                         checkbox_switch=True,
+                                         checkbox_field=ISearchManagerInfo[
+                                             'enable_collections_search']))
+        super(SearchManagerPropertiesEditForm, self).updateGroups()
\ No newline at end of file
--- a/src/pyams_content/generations/__init__.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/generations/__init__.py	Fri Jun 26 12:54:13 2020 +0200
@@ -330,7 +330,7 @@
     """PyAMS content package generations checker"""
 
     order = 100
-    generation = 2
+    generation = 3
 
     def evolve(self, site, current=None):
         """Check for required utilities, tables and tools"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/generations/evolve2.py	Fri Jun 26 12:54:13 2020 +0200
@@ -0,0 +1,52 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+from zope.annotation import IAnnotations
+
+from pyams_content.component.theme import COLLECTIONS_MANAGER_KEY, ICollectionsManager, \
+    ITagsManager
+from pyams_content.features.search.interfaces import ISearchManagerInfo
+from pyams_content.shared.resource.interfaces import IResourceManager
+from pyams_utils.registry import get_local_registry, query_utility, set_local_registry
+
+__docformat__ = 'restructuredtext'
+
+
+def evolve(site):
+    """Evolve 3: update search settings"""
+    registry = get_local_registry()
+    try:
+        set_local_registry(site.getSiteManager())
+        # tags search updates
+        tags_info = ITagsManager(site)
+        search_info = ISearchManagerInfo(site)
+        if getattr(tags_info, 'enable_tags_search', None) is not None:
+            print("Updating tags search settings...")
+            search_info.enable_tags_search = tags_info.enable_tags_search
+            search_info.tags_search_target = tags_info.tags_search_target
+            del tags_info.tags_search_target
+            del tags_info.enable_tags_search
+        # collections settings updates from resources settings
+        resources = query_utility(IResourceManager)
+        if resources is not None:
+            annotations = IAnnotations(resources)
+            res_collections_info = annotations.get(COLLECTIONS_MANAGER_KEY)
+            if res_collections_info is not None:
+                print("Updating collections settings...")
+                root_collections_info = ICollectionsManager(site)
+                if getattr(res_collections_info, 'thesaurus_name', None) is not None:
+                    root_collections_info.thesaurus_name = res_collections_info.thesaurus_name
+                    root_collections_info.extract_name = getattr(res_collections_info,
+                                                                 'extract_name', None)
+                del annotations[COLLECTIONS_MANAGER_KEY]
+    finally:
+        set_local_registry(registry)
Binary file src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.mo has changed
--- a/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po	Fri Jun 26 12:54:13 2020 +0200
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2020-06-19 18:01+0200\n"
+"POT-Creation-Date: 2020-06-24 12:02+0200\n"
 "PO-Revision-Date: 2015-09-10 10:42+0200\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French\n"
@@ -2120,9 +2120,9 @@
 msgid "Add new illustration"
 msgstr "Ajout d'une illustration"
 
-#: src/pyams_content/component/theme/__init__.py:74
-#: src/pyams_content/component/theme/interfaces.py:73
-#: src/pyams_content/component/theme/interfaces.py:87
+#: src/pyams_content/component/theme/__init__.py:76
+#: src/pyams_content/component/theme/interfaces.py:74
+#: src/pyams_content/component/theme/interfaces.py:88
 #: src/pyams_content/component/theme/zmi/portlet.py:40
 #: src/pyams_content/root/zmi/search.py:177
 #: src/pyams_content/root/zmi/templates/advanced-search.pt:181
@@ -2130,31 +2130,31 @@
 msgid "Tags"
 msgstr "Tags"
 
-#: src/pyams_content/component/theme/__init__.py:124
-#: src/pyams_content/component/theme/interfaces.py:114
-#: src/pyams_content/component/theme/interfaces.py:128
+#: src/pyams_content/component/theme/__init__.py:126
+#: src/pyams_content/component/theme/interfaces.py:115
+#: src/pyams_content/component/theme/interfaces.py:129
 #: src/pyams_content/component/theme/zmi/portlet.py:55
 #: src/pyams_content/shared/common/zmi/search.py:197
 msgid "Themes"
 msgstr "Thèmes"
 
-#: src/pyams_content/component/theme/__init__.py:174
-#: src/pyams_content/component/theme/interfaces.py:155
-#: src/pyams_content/component/theme/interfaces.py:169
+#: src/pyams_content/component/theme/__init__.py:179
+#: src/pyams_content/component/theme/interfaces.py:175
+#: src/pyams_content/component/theme/interfaces.py:189
 #: src/pyams_content/component/theme/zmi/portlet.py:70
 #: src/pyams_content/shared/common/zmi/search.py:200
 msgid "Collections"
 msgstr "Collections"
 
-#: src/pyams_content/component/theme/__init__.py:83
+#: src/pyams_content/component/theme/__init__.py:85
 msgid "no defined tag"
 msgstr "aucun tag défini"
 
-#: src/pyams_content/component/theme/__init__.py:133
+#: src/pyams_content/component/theme/__init__.py:135
 msgid "no defined theme"
 msgstr "aucun thème défini"
 
-#: src/pyams_content/component/theme/__init__.py:183
+#: src/pyams_content/component/theme/__init__.py:188
 msgid "no defined collection"
 msgstr "aucune collection définie"
 
@@ -2178,26 +2178,52 @@
 msgid "Site or folder where tags search is displayed"
 msgstr "Site ou rubrique où la recherche par tags s'effectue"
 
-#: src/pyams_content/component/theme/interfaces.py:51
+#: src/pyams_content/component/theme/interfaces.py:52
 msgid "Enable glossary?"
 msgstr "Activer le glossaire ?"
 
-#: src/pyams_content/component/theme/interfaces.py:56
+#: src/pyams_content/component/theme/interfaces.py:57
 msgid "Glossary thesaurus name"
 msgstr "Thesaurus du glossaire"
 
-#: src/pyams_content/component/theme/interfaces.py:49
+#: src/pyams_content/component/theme/interfaces.py:148
+msgid "Enable search by collection?"
+msgstr "Activer la recherche par collection ?"
+
+#: src/pyams_content/component/theme/interfaces.py:149
+msgid ""
+"If 'yes', displayed collections will lead to a search engine displaying "
+"contents matching given collection"
+msgstr ""
+"Si 'oui', un clic sur un nom de collection permet d'accéder à une page de résultat de "
+"recherche portant sur la collection sélectionnée"
+
+#: src/pyams_content/component/theme/interfaces.py:155
+msgid "Collections search target"
+msgstr "Cible de la recherche"
+
+#: src/pyams_content/component/theme/interfaces.py:156
+msgid "Site or folder where collections search is displayed"
+msgstr "Site ou rubrique où la recherche par collections s'effectue"
+
+#: src/pyams_content/component/theme/interfaces.py:50
 msgid "You must specify search target when activating search by tags!"
 msgstr ""
 "Vous devez indiquer la cible de la recherche lorsque vous activez la "
 "recherche par tags !"
 
-#: src/pyams_content/component/theme/interfaces.py:63
+#: src/pyams_content/component/theme/interfaces.py:64
 msgid "You must specify a glossary thesaurus to activate it!"
 msgstr ""
 "Vous devez indiquer le nom du thésaurus contenant les termes du glossaire "
 "pour pouvoir l'activer !"
 
+#: src/pyams_content/component/theme/interfaces.py:164
+msgid "You must specify search target when activating search by collections!"
+msgstr ""
+"Vous devez indiquer la cible de la recherche lorsque vous activez la "
+"recherche par collections !"
+
 #: src/pyams_content/component/theme/zmi/manager.py:46
 msgid "Tags settings..."
 msgstr "Paramétrage des tags"
@@ -2206,30 +2232,34 @@
 msgid "Selected tags"
 msgstr "Tags sélectionnés"
 
-#: src/pyams_content/component/theme/zmi/manager.py:117
+#: src/pyams_content/component/theme/zmi/manager.py:118
 msgid "Themes settings..."
 msgstr "Paramétrage des thèmes"
 
-#: src/pyams_content/component/theme/zmi/manager.py:131
+#: src/pyams_content/component/theme/zmi/manager.py:132
 msgid "Selected themes"
 msgstr "Thèmes sélectionnés"
 
-#: src/pyams_content/component/theme/zmi/manager.py:167
+#: src/pyams_content/component/theme/zmi/manager.py:169
 msgid "Collections settings..."
 msgstr "Paramétrage des collections"
 
-#: src/pyams_content/component/theme/zmi/manager.py:181
+#: src/pyams_content/component/theme/zmi/manager.py:183
 msgid "Selected collections"
 msgstr "Collections sélectionnées"
 
-#: src/pyams_content/component/theme/zmi/manager.py:93
+#: src/pyams_content/component/theme/zmi/manager.py:94
 msgid "Enable search by tag"
 msgstr "Activer la recherche par tag"
 
-#: src/pyams_content/component/theme/zmi/manager.py:100
+#: src/pyams_content/component/theme/zmi/manager.py:101
 msgid "Enable glossary"
 msgstr "Activer le glossaire"
 
+#: src/pyams_content/component/theme/zmi/manager.py:217
+msgid "Enable search by collection"
+msgstr "Activer la recherche par collection"
+
 #: src/pyams_content/component/theme/zmi/__init__.py:55
 #: src/pyams_content/shared/view/zmi/theme.py:49
 msgid "Tags..."
@@ -2348,7 +2378,7 @@
 #: src/pyams_content/features/share/zmi/container.py:155
 #: src/pyams_content/features/menu/zmi/__init__.py:217
 #: src/pyams_content/shared/form/interfaces.py:121
-#: src/pyams_content/shared/form/zmi/field.py:166
+#: src/pyams_content/shared/form/zmi/field.py:172
 #: src/pyams_content/shared/common/interfaces/types.py:45
 msgid "Label"
 msgstr "Libellé"
@@ -3396,7 +3426,7 @@
 msgid "Rich text description"
 msgstr "Texte enrichi"
 
-#: src/pyams_content/features/thesaurus/interfaces.py:28
+#: src/pyams_content/features/thesaurus/interfaces.py:30
 msgid "HTML content"
 msgstr "Contenu HTML"
 
@@ -4220,7 +4250,7 @@
 "Nom interne du champ ; ce nom doit être unique pour un formulaire donné"
 
 #: src/pyams_content/shared/form/interfaces.py:116
-#: src/pyams_content/shared/form/zmi/field.py:177
+#: src/pyams_content/shared/form/zmi/field.py:184
 msgid "Field type"
 msgstr "Type de champ"
 
@@ -4492,37 +4522,37 @@
 msgid "« {handler} » form handler settings"
 msgstr "Paramètres du gestionnaire « {handler} »"
 
-#: src/pyams_content/shared/form/zmi/field.py:67
+#: src/pyams_content/shared/form/zmi/field.py:68
 msgid "Form fields..."
 msgstr "Champs de saisie"
 
-#: src/pyams_content/shared/form/zmi/field.py:155
+#: src/pyams_content/shared/form/zmi/field.py:160
 #: src/pyams_content/shared/common/interfaces/types.py:41
 msgid "Name"
 msgstr "Nom"
 
-#: src/pyams_content/shared/form/zmi/field.py:210
+#: src/pyams_content/shared/form/zmi/field.py:219
 msgid "Form fields list"
 msgstr "Liste des champs du formulaire"
 
-#: src/pyams_content/shared/form/zmi/field.py:233
-#: src/pyams_content/shared/form/zmi/field.py:246
+#: src/pyams_content/shared/form/zmi/field.py:243
+#: src/pyams_content/shared/form/zmi/field.py:256
 msgid "Add form field"
 msgstr "Ajouter un champ"
 
-#: src/pyams_content/shared/form/zmi/field.py:284
+#: src/pyams_content/shared/form/zmi/field.py:295
 msgid "Edit form field properties"
 msgstr "Propriétés du champ"
 
-#: src/pyams_content/shared/form/zmi/field.py:186
+#: src/pyams_content/shared/form/zmi/field.py:193
 msgid "-- unknown field type --"
 msgstr "-- type de champ inconnu --"
 
-#: src/pyams_content/shared/form/zmi/field.py:121
+#: src/pyams_content/shared/form/zmi/field.py:122
 msgid "No currently defined form field."
 msgstr "Ce formulaire ne comporte aucun champ."
 
-#: src/pyams_content/shared/form/zmi/field.py:268
+#: src/pyams_content/shared/form/zmi/field.py:278
 msgid "Specified name is already used!"
 msgstr "Le nom indiqué pour ce champ est déjà utilisé !"
 
@@ -5453,8 +5483,8 @@
 "If 'yes', content publication date will be displayed; the selected "
 "publication date is those which is selected while publishing the content"
 msgstr ""
-"Si 'oui', la date de publication du contenu sera affichée; la date "
-"affichée est celle qui a été sélectionnée au moment de la publication"
+"Si 'oui', la date de publication du contenu sera affichée; la date affichée "
+"est celle qui a été sélectionnée au moment de la publication"
 
 #: src/pyams_content/shared/common/portlet/interfaces.py:49
 #: src/pyams_content/shared/common/portlet/interfaces.py:72
@@ -5464,8 +5494,7 @@
 #: src/pyams_content/shared/common/portlet/interfaces.py:50
 #: src/pyams_content/shared/common/portlet/interfaces.py:73
 msgid "This text will be displayed before publication date"
-msgstr ""
-"Ce texte sera affiché avant la date de publication"
+msgstr "Ce texte sera affiché avant la date de publication"
 
 #: src/pyams_content/shared/common/portlet/interfaces.py:54
 msgid "Display specificities?"
--- a/src/pyams_content/locales/pyams_content.pot	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/locales/pyams_content.pot	Fri Jun 26 12:54:13 2020 +0200
@@ -6,7 +6,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2020-06-19 18:01+0200\n"
+"POT-Creation-Date: 2020-06-24 12:02+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -1996,9 +1996,9 @@
 msgid "Add new illustration"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/__init__.py:74
-#: ./src/pyams_content/component/theme/interfaces.py:73
-#: ./src/pyams_content/component/theme/interfaces.py:87
+#: ./src/pyams_content/component/theme/__init__.py:76
+#: ./src/pyams_content/component/theme/interfaces.py:74
+#: ./src/pyams_content/component/theme/interfaces.py:88
 #: ./src/pyams_content/component/theme/zmi/portlet.py:40
 #: ./src/pyams_content/root/zmi/search.py:177
 #: ./src/pyams_content/root/zmi/templates/advanced-search.pt:181
@@ -2006,31 +2006,31 @@
 msgid "Tags"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/__init__.py:124
-#: ./src/pyams_content/component/theme/interfaces.py:114
-#: ./src/pyams_content/component/theme/interfaces.py:128
+#: ./src/pyams_content/component/theme/__init__.py:126
+#: ./src/pyams_content/component/theme/interfaces.py:115
+#: ./src/pyams_content/component/theme/interfaces.py:129
 #: ./src/pyams_content/component/theme/zmi/portlet.py:55
 #: ./src/pyams_content/shared/common/zmi/search.py:197
 msgid "Themes"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/__init__.py:174
-#: ./src/pyams_content/component/theme/interfaces.py:155
-#: ./src/pyams_content/component/theme/interfaces.py:169
+#: ./src/pyams_content/component/theme/__init__.py:179
+#: ./src/pyams_content/component/theme/interfaces.py:175
+#: ./src/pyams_content/component/theme/interfaces.py:189
 #: ./src/pyams_content/component/theme/zmi/portlet.py:70
 #: ./src/pyams_content/shared/common/zmi/search.py:200
 msgid "Collections"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/__init__.py:83
+#: ./src/pyams_content/component/theme/__init__.py:85
 msgid "no defined tag"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/__init__.py:133
+#: ./src/pyams_content/component/theme/__init__.py:135
 msgid "no defined theme"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/__init__.py:183
+#: ./src/pyams_content/component/theme/__init__.py:188
 msgid "no defined collection"
 msgstr ""
 
@@ -2052,22 +2052,44 @@
 msgid "Site or folder where tags search is displayed"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/interfaces.py:51
+#: ./src/pyams_content/component/theme/interfaces.py:52
 msgid "Enable glossary?"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/interfaces.py:56
+#: ./src/pyams_content/component/theme/interfaces.py:57
 msgid "Glossary thesaurus name"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/interfaces.py:49
+#: ./src/pyams_content/component/theme/interfaces.py:148
+msgid "Enable search by collection?"
+msgstr ""
+
+#: ./src/pyams_content/component/theme/interfaces.py:149
+msgid ""
+"If 'yes', displayed collections will lead to a search engine displaying "
+"contents matching given collection"
+msgstr ""
+
+#: ./src/pyams_content/component/theme/interfaces.py:155
+msgid "Collections search target"
+msgstr ""
+
+#: ./src/pyams_content/component/theme/interfaces.py:156
+msgid "Site or folder where collections search is displayed"
+msgstr ""
+
+#: ./src/pyams_content/component/theme/interfaces.py:50
 msgid "You must specify search target when activating search by tags!"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/interfaces.py:63
+#: ./src/pyams_content/component/theme/interfaces.py:64
 msgid "You must specify a glossary thesaurus to activate it!"
 msgstr ""
 
+#: ./src/pyams_content/component/theme/interfaces.py:164
+msgid "You must specify search target when activating search by collections!"
+msgstr ""
+
 #: ./src/pyams_content/component/theme/zmi/manager.py:46
 msgid "Tags settings..."
 msgstr ""
@@ -2076,30 +2098,34 @@
 msgid "Selected tags"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/zmi/manager.py:117
+#: ./src/pyams_content/component/theme/zmi/manager.py:118
 msgid "Themes settings..."
 msgstr ""
 
-#: ./src/pyams_content/component/theme/zmi/manager.py:131
+#: ./src/pyams_content/component/theme/zmi/manager.py:132
 msgid "Selected themes"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/zmi/manager.py:167
+#: ./src/pyams_content/component/theme/zmi/manager.py:169
 msgid "Collections settings..."
 msgstr ""
 
-#: ./src/pyams_content/component/theme/zmi/manager.py:181
+#: ./src/pyams_content/component/theme/zmi/manager.py:183
 msgid "Selected collections"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/zmi/manager.py:93
+#: ./src/pyams_content/component/theme/zmi/manager.py:94
 msgid "Enable search by tag"
 msgstr ""
 
-#: ./src/pyams_content/component/theme/zmi/manager.py:100
+#: ./src/pyams_content/component/theme/zmi/manager.py:101
 msgid "Enable glossary"
 msgstr ""
 
+#: ./src/pyams_content/component/theme/zmi/manager.py:217
+msgid "Enable search by collection"
+msgstr ""
+
 #: ./src/pyams_content/component/theme/zmi/__init__.py:55
 #: ./src/pyams_content/shared/view/zmi/theme.py:49
 msgid "Tags..."
@@ -2209,7 +2235,7 @@
 #: ./src/pyams_content/features/share/zmi/container.py:155
 #: ./src/pyams_content/features/menu/zmi/__init__.py:217
 #: ./src/pyams_content/shared/form/interfaces.py:121
-#: ./src/pyams_content/shared/form/zmi/field.py:166
+#: ./src/pyams_content/shared/form/zmi/field.py:172
 #: ./src/pyams_content/shared/common/interfaces/types.py:45
 msgid "Label"
 msgstr ""
@@ -3124,7 +3150,7 @@
 msgid "Rich text description"
 msgstr ""
 
-#: ./src/pyams_content/features/thesaurus/interfaces.py:28
+#: ./src/pyams_content/features/thesaurus/interfaces.py:30
 msgid "HTML content"
 msgstr ""
 
@@ -3932,7 +3958,7 @@
 msgstr ""
 
 #: ./src/pyams_content/shared/form/interfaces.py:116
-#: ./src/pyams_content/shared/form/zmi/field.py:177
+#: ./src/pyams_content/shared/form/zmi/field.py:184
 msgid "Field type"
 msgstr ""
 
@@ -4167,37 +4193,37 @@
 msgid "« {handler} » form handler settings"
 msgstr ""
 
-#: ./src/pyams_content/shared/form/zmi/field.py:67
+#: ./src/pyams_content/shared/form/zmi/field.py:68
 msgid "Form fields..."
 msgstr ""
 
-#: ./src/pyams_content/shared/form/zmi/field.py:155
+#: ./src/pyams_content/shared/form/zmi/field.py:160
 #: ./src/pyams_content/shared/common/interfaces/types.py:41
 msgid "Name"
 msgstr ""
 
-#: ./src/pyams_content/shared/form/zmi/field.py:210
+#: ./src/pyams_content/shared/form/zmi/field.py:219
 msgid "Form fields list"
 msgstr ""
 
-#: ./src/pyams_content/shared/form/zmi/field.py:233
-#: ./src/pyams_content/shared/form/zmi/field.py:246
+#: ./src/pyams_content/shared/form/zmi/field.py:243
+#: ./src/pyams_content/shared/form/zmi/field.py:256
 msgid "Add form field"
 msgstr ""
 
-#: ./src/pyams_content/shared/form/zmi/field.py:284
+#: ./src/pyams_content/shared/form/zmi/field.py:295
 msgid "Edit form field properties"
 msgstr ""
 
-#: ./src/pyams_content/shared/form/zmi/field.py:186
+#: ./src/pyams_content/shared/form/zmi/field.py:193
 msgid "-- unknown field type --"
 msgstr ""
 
-#: ./src/pyams_content/shared/form/zmi/field.py:121
+#: ./src/pyams_content/shared/form/zmi/field.py:122
 msgid "No currently defined form field."
 msgstr ""
 
-#: ./src/pyams_content/shared/form/zmi/field.py:268
+#: ./src/pyams_content/shared/form/zmi/field.py:278
 msgid "Specified name is already used!"
 msgstr ""
 
--- a/src/pyams_content/root/__init__.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/root/__init__.py	Fri Jun 26 12:54:13 2020 +0200
@@ -12,13 +12,12 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
 from persistent import Persistent
 from pyramid.events import subscriber
 from zope.interface import implementer
 
 from pyams_content.component.illustration.interfaces import IIllustrationTarget
+from pyams_content.component.theme import ICollectionsManagerTarget
 from pyams_content.component.theme.interfaces import ITagsManagerTarget
 from pyams_content.features.alert.interfaces import IAlertTarget
 from pyams_content.features.footer.interfaces import IFooterTarget
@@ -27,11 +26,12 @@
 from pyams_content.features.redirect.interfaces import IRedirectionManagerTarget
 from pyams_content.features.share.interfaces import ISocialShareManagerTarget
 from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION, OPERATOR_ROLE, WEBMASTER_ROLE
-from pyams_content.root.interfaces import ISiteRoot, ISiteRootBackOfficeConfiguration, ISiteRootConfiguration, \
-    ISiteRootRoles, ISiteRootToolsConfiguration
+from pyams_content.root.interfaces import ISiteRoot, ISiteRootBackOfficeConfiguration, \
+    ISiteRootConfiguration, ISiteRootRoles, ISiteRootToolsConfiguration
 from pyams_form.interfaces.form import IFormContextPermissionChecker
 from pyams_portal.interfaces import DESIGNER_ROLE, IPortalContext
-from pyams_security.interfaces import IDefaultProtectionPolicy, IGrantedRoleEvent, ISecurityManager, SYSTEM_ADMIN_ROLE
+from pyams_security.interfaces import IDefaultProtectionPolicy, IGrantedRoleEvent, \
+    ISecurityManager, SYSTEM_ADMIN_ROLE
 from pyams_security.property import RolePrincipalsFieldProperty
 from pyams_security.security import ProtectedObject
 from pyams_skin.configuration import BackOfficeConfiguration, Configuration
@@ -44,12 +44,16 @@
 from pyams_utils.site import BaseSiteRoot
 from pyams_utils.traversing import get_parent
 
+
+__docformat__ = 'restructuredtext'
+
 from pyams_content import _
 
 
-@implementer(IDefaultProtectionPolicy, ISiteRoot, ISiteRootRoles, IPortalContext, ITagsManagerTarget,
-             IIllustrationTarget, IHeaderTarget, IFooterTarget, ISocialShareManagerTarget, IAlertTarget,
-             IRedirectionManagerTarget, IPreviewTarget)
+@implementer(IDefaultProtectionPolicy, ISiteRoot, ISiteRootRoles, IPortalContext,
+             ITagsManagerTarget, ICollectionsManagerTarget, IIllustrationTarget,
+             IHeaderTarget, IFooterTarget, ISocialShareManagerTarget,
+             IAlertTarget, IRedirectionManagerTarget, IPreviewTarget)
 class SiteRoot(ProtectedObject, BaseSiteRoot, UserSkinnableContent):
     """Main site root"""
 
--- a/src/pyams_content/shared/resource/manager.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/shared/resource/manager.py	Fri Jun 26 12:54:13 2020 +0200
@@ -17,7 +17,7 @@
 from zope.schema.fieldproperty import FieldProperty
 
 from pyams_content.component.paragraph import IParagraphFactorySettings
-from pyams_content.component.theme import ICollectionsManagerTarget, IThemesManagerTarget
+from pyams_content.component.theme import IThemesManagerTarget
 from pyams_content.reference.pictograms.interfaces import IPictogramManagerTarget
 from pyams_content.shared.common.interfaces import ISharedContentFactory
 from pyams_content.shared.common.manager import SharedTool
@@ -33,7 +33,7 @@
 
 
 @implementer(IResourceManager, IParagraphFactorySettings, IPictogramManagerTarget,
-             IThemesManagerTarget, ICollectionsManagerTarget)
+             IThemesManagerTarget)
 class ResourceManager(SharedTool, TypedSharedToolMixin):
     """Resource manager class"""
 
--- a/src/pyams_content/shared/view/interfaces.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/shared/view/interfaces.py	Fri Jun 26 12:54:13 2020 +0200
@@ -287,7 +287,8 @@
     """View collections settings"""
 
     select_context_collections = Bool(title=_("Select context collections?"),
-                                      description=_("If 'yes', collections will be extracted from context"),
+                                      description=_("If 'yes', collections will be extracted "
+                                                    "from context"),
                                       required=False,
                                       default=False)
 
--- a/src/pyams_content/shared/view/theme.py	Wed Jun 24 12:07:40 2020 +0200
+++ b/src/pyams_content/shared/view/theme.py	Fri Jun 26 12:54:13 2020 +0200
@@ -19,12 +19,13 @@
 from zope.intid.interfaces import IIntIds
 from zope.schema.fieldproperty import FieldProperty
 
-from pyams_content.component.theme import ICollectionsInfo, ITagsManager
+from pyams_content.component.theme import ICollectionsInfo, ICollectionsManager, ITagsManager
 from pyams_content.component.theme.interfaces import ITagsInfo, IThemesInfo
 from pyams_content.features.search import SearchFolderQuery
-from pyams_content.shared.view.interfaces import IViewCollectionsSettings, IViewQueryParamsExtension, IViewSettings, \
-    IViewTagsSettings, IViewThemesSettings, IViewUserQuery, IWfView, VIEW_COLLECTIONS_SETTINGS_KEY, \
-    VIEW_TAGS_SETTINGS_KEY, VIEW_THEMES_SETTINGS_KEY
+from pyams_content.shared.view.interfaces import IViewCollectionsSettings, \
+    IViewQueryParamsExtension, IViewSettings, IViewTagsSettings, IViewThemesSettings, \
+    IViewUserQuery, IWfView, VIEW_COLLECTIONS_SETTINGS_KEY, VIEW_TAGS_SETTINGS_KEY, \
+    VIEW_THEMES_SETTINGS_KEY
 from pyams_thesaurus.interfaces.thesaurus import IThesaurus
 from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
 from pyams_utils.factory import factory_config
@@ -215,3 +216,24 @@
             yield Any(catalog['collections'], collections)
         elif settings.select_context_collections:
             yield None
+
+
+@adapter_config(name='collections', context=SearchFolderQuery, provides=IViewUserQuery)
+class SearchFolderCollectionQuery(ContextAdapter):
+    """Search folder collections query"""
+
+    @staticmethod
+    def get_user_params(request):
+        collection = request.params.get('collection')
+        if collection:
+            manager = ICollectionsManager(request.root, None)
+            if manager is None:
+                raise StopIteration
+            thesaurus = query_utility(IThesaurus, name=manager.thesaurus_name)
+            if thesaurus is None:
+                raise StopIteration
+            term = thesaurus.terms.get(collection)
+            if term is not None:
+                catalog = get_utility(ICatalog)
+                intids = query_utility(IIntIds)
+                yield Any(catalog['collections'], (intids.queryId(term),))