Update views and search engine so that results displayed in a view component can be excluded from other search results
authorThierry Florac <tflorac@ulthar.net>
Fri, 14 Dec 2018 13:32:54 +0100
changeset 1171 41310cefa42a
parent 1170 49cba50f36cb
child 1172 7724c2dd7a82
Update views and search engine so that results displayed in a view component can be excluded from other search results
src/pyams_content/features/search/__init__.py
src/pyams_content/shared/view/portlet/__init__.py
src/pyams_content/shared/view/portlet/interfaces.py
src/pyams_content/shared/view/reference.py
--- a/src/pyams_content/features/search/__init__.py	Fri Dec 14 11:59:12 2018 +0100
+++ b/src/pyams_content/features/search/__init__.py	Fri Dec 14 13:32:54 2018 +0100
@@ -9,11 +9,13 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
+from pyams_content.shared.view.portlet import SEARCH_EXCLUDED_ITEMS
+
 
 __docformat__ = 'restructuredtext'
 
 from hypatia.interfaces import ICatalog
-from hypatia.query import Contains, Or
+from hypatia.query import Contains, Or, NotAny
 from zope.interface import implementer
 from zope.schema.fieldproperty import FieldProperty
 
@@ -89,9 +91,30 @@
         return params
 
 
+@adapter_config(name='exclusions', context=SearchFolderQuery, provides=IViewUserQuery)
+class SearchFolderExclusionsQuery(ContextAdapter):
+    """Search folder exclusions query
+
+    This adapter is looking into request's annotations for items which should be excluded
+    from search.
+    """
+
+    @staticmethod
+    def get_user_params(request):
+        # check for results excluded by previous views
+        if request is not None:
+            excluded_items = request.annotations.get(SEARCH_EXCLUDED_ITEMS)
+            if excluded_items:
+                catalog = get_utility(ICatalog)
+                yield NotAny(catalog['oid'], excluded_items)
+
+
 @adapter_config(name='user_search', context=SearchFolderQuery, provides=IViewUserQuery)
 class SearchFolderUserQuery(ContextAdapter):
-    """Search folder user query"""
+    """Search folder user query
+
+    This adapter is looking for any fulltext search entered by user
+    """
 
     @staticmethod
     def get_user_params(request):
--- a/src/pyams_content/shared/view/portlet/__init__.py	Fri Dec 14 11:59:12 2018 +0100
+++ b/src/pyams_content/shared/view/portlet/__init__.py	Fri Dec 14 13:32:54 2018 +0100
@@ -12,14 +12,16 @@
 
 __docformat__ = 'restructuredtext'
 
-from itertools import islice
+from itertools import islice, tee
 
 from zope.schema.fieldproperty import FieldProperty
 
 from pyams_content.shared.view.interfaces import IViewsManager, IViewsMerger
-from pyams_content.shared.view.portlet.interfaces import IViewItemsPortletSettings, VIEW_DISPLAY_CONTEXT
+from pyams_content.shared.view.portlet.interfaces import IViewItemsPortletSettings, VIEW_DISPLAY_CONTEXT, \
+    SEARCH_EXCLUDED_ITEMS
 from pyams_portal.interfaces import PREVIEW_MODE
 from pyams_portal.portlet import Portlet, PortletSettings, portlet_config
+from pyams_sequence.interfaces import ISequentialIdInfo
 from pyams_sequence.reference import get_sequence_target
 from pyams_utils.factory import factory_config
 from pyams_utils.interfaces import VIEW_PERMISSION
@@ -45,6 +47,7 @@
     views_merge_mode = FieldProperty(IViewItemsPortletSettings['views_merge_mode'])
     limit = FieldProperty(IViewItemsPortletSettings['limit'])
     start = FieldProperty(IViewItemsPortletSettings['start'])
+    exclude_from_search = FieldProperty(IViewItemsPortletSettings['exclude_from_search'])
 
     def get_views(self):
         views_manager = get_utility(IViewsManager)
@@ -71,12 +74,18 @@
         merger = self.get_merger(request)
         if merger is not None:
             start = int(request.params.get('start', 0))
-            yield from islice(unique_iter(merger.get_results(self.get_views(),
-                                                             context,
-                                                             ignore_cache=ignore_cache,
-                                                             request=request)),
-                              start + (self.start or 1) - 1,
-                              min(limit or 999, self.limit or 999))
+            items = islice(unique_iter(merger.get_results(self.get_views(),
+                                                          context,
+                                                          ignore_cache=ignore_cache,
+                                                          request=request)),
+                           start + (self.start or 1) - 1,
+                           min(limit or 999, self.limit or 999))
+            if (request is not None) and self.exclude_from_search:
+                (excluded, items) = tee(items)
+                excluded_items = request.annotations.get(SEARCH_EXCLUDED_ITEMS) or set()
+                excluded_items |= set((ISequentialIdInfo(item).hex_oid for item in excluded))
+                request.annotations[SEARCH_EXCLUDED_ITEMS] = excluded_items
+            yield from items
 
 
 @portlet_config(permission=VIEW_PERMISSION)
--- a/src/pyams_content/shared/view/portlet/interfaces.py	Fri Dec 14 11:59:12 2018 +0100
+++ b/src/pyams_content/shared/view/portlet/interfaces.py	Fri Dec 14 13:32:54 2018 +0100
@@ -12,20 +12,15 @@
 
 __docformat__ = 'restructuredtext'
 
+from zope.schema import Bool, Choice, Int
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 
-# import standard library
-
-# import interfaces
+from pyams_content.shared.view import WfView
 from pyams_content.shared.view.interfaces import VIEWS_MERGERS_VOCABULARY
-from pyams_portal.interfaces import IPortletSettings
-
-# import packages
-from pyams_content.shared.view import WfView
 from pyams_content.shared.view.merge import CONCAT_VIEWS_MERGE_MODE
 from pyams_i18n.schema import I18nTextLineField
+from pyams_portal.interfaces import IPortletSettings
 from pyams_sequence.schema import InternalReferencesListField
-from zope.schema import Choice, Int
-from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
 
 from pyams_content import _
 
@@ -45,6 +40,8 @@
 VIEW_CONTEXT_VOCABULARY = SimpleVocabulary([SimpleTerm(item['id'], title=item['title'])
                                             for item in VIEW_CONTEXTS])
 
+SEARCH_EXCLUDED_ITEMS = 'search.excluded'
+
 
 #
 # Views merge modes
@@ -99,3 +96,10 @@
 
     def get_items(self):
         """Get iterator over items returned by selected views, using selected merger"""
+
+    exclude_from_search = Bool(title=_("Exclude from search results"),
+                               description=_("If 'yes', and if this portlet is associated with a search engine in the "
+                                             "same page template, items displayed by this portlet will be excluded "
+                                             "from search results"),
+                               required=True,
+                               default=False)
--- a/src/pyams_content/shared/view/reference.py	Fri Dec 14 11:59:12 2018 +0100
+++ b/src/pyams_content/shared/view/reference.py	Fri Dec 14 13:32:54 2018 +0100
@@ -10,6 +10,7 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
+
 __docformat__ = 'restructuredtext'
 
 from hypatia.catalog import CatalogQuery
@@ -22,8 +23,8 @@
 
 from pyams_catalog.query import CatalogResultSet
 from pyams_content.shared.view.interfaces import ALWAYS_REFERENCE_MODE, IViewInternalReferencesSettings, \
-    IViewQueryFilterExtension, IViewQueryParamsExtension, IViewSettings, IWfView, VIEW_REFERENCES_SETTINGS_KEY, \
-    ONLY_REFERENCE_MODE
+    IViewQueryFilterExtension, IViewQueryParamsExtension, IViewSettings, IWfView, ONLY_REFERENCE_MODE, \
+    VIEW_REFERENCES_SETTINGS_KEY
 from pyams_content.workflow import VISIBLE_STATES
 from pyams_sequence.interfaces import ISequentialIdInfo
 from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
@@ -59,16 +60,15 @@
 
     def get_params(self, context, request=None):
         settings = IViewInternalReferencesSettings(self.context)
+        catalog = get_utility(ICatalog)
         # check view settings
         if settings.exclude_context:
             sequence = ISequentialIdInfo(context, None)
             if sequence is not None:
                 oid = sequence.hex_oid
-                catalog = get_utility(ICatalog)
                 yield NotEq(catalog['oid'], oid)
         # check view references mode
         if settings.references_mode == ONLY_REFERENCE_MODE:
-            catalog = get_utility(ICatalog)
             yield Any(catalog['oid'], settings.references), False