Added aggregations support to views
authorThierry Florac <tflorac@ulthar.net>
Mon, 11 Jan 2021 16:45:22 +0100
changeset 1443 2af30495d1d7
parent 1442 22716baab0fe
child 1444 3f7ee0f3e1b5
Added aggregations support to views
src/pyams_content/features/search/__init__.py
src/pyams_content/features/search/portlet/__init__.py
src/pyams_content/features/search/portlet/interfaces.py
src/pyams_content/features/search/portlet/zmi/templates/search-preview.pt
src/pyams_content/shared/view/__init__.py
src/pyams_content/shared/view/interfaces.py
--- a/src/pyams_content/features/search/__init__.py	Tue Dec 01 09:19:16 2020 +0100
+++ b/src/pyams_content/features/search/__init__.py	Mon Jan 11 16:45:22 2021 +0100
@@ -64,12 +64,15 @@
         return True
 
     def get_results(self, context, sort_index=None, reverse=None, limit=None,
-                    start=0, length=None, ignore_cache=False, get_count=False, request=None):
+                    start=0, length=None, ignore_cache=False, get_count=False, request=None,
+                    aggregates=None):
         if not ignore_cache:
-            request = check_request()
+            if request is None:
+                request = check_request()
             ignore_cache = bool(request.params)
         return super(SearchFolder, self).get_results(context, sort_index, reverse, limit, start,
-                                                     length, ignore_cache, get_count, request)
+                                                     length, ignore_cache, get_count, request,
+                                                     aggregates)
 
 
 @adapter_config(context=ISearchFolder, provides=IFormContextPermissionChecker)
@@ -143,3 +146,15 @@
                                 Contains(index, ' and '.join((w + '*'
                                                               for w in fulltext.split()))))
                 yield Or(*query_params)
+
+
+@adapter_config(name='content_type', context=SearchFolderQuery, provides=IViewUserQuery)
+class SearchFolderContentTypeQuery(ContextAdapter):
+    """Search folder content-type query"""
+
+    @staticmethod
+    def get_user_params(request):
+        content_type = request.params.get('content_type')
+        if content_type:
+            catalog = get_utility(ICatalog)
+            yield Eq(catalog['content_type'], content_type)
--- a/src/pyams_content/features/search/portlet/__init__.py	Tue Dec 01 09:19:16 2020 +0100
+++ b/src/pyams_content/features/search/portlet/__init__.py	Mon Jan 11 16:45:22 2021 +0100
@@ -19,8 +19,10 @@
 
 from pyams_content import _
 from pyams_content.features.search import ISearchFolder
-from pyams_content.features.search.portlet.interfaces import ISearchResultsPortletSettings
+from pyams_content.features.search.portlet.interfaces import IAggregatedPortletRenderer, \
+    ISearchResultsPortletSettings
 from pyams_content.shared.view.interfaces import RELEVANCE_ORDER, VISIBLE_PUBLICATION_DATE_ORDER
+from pyams_portal.interfaces import IPortletRendererSettings
 from pyams_portal.portlet import Portlet, PortletSettings, portlet_config
 from pyams_utils.factory import factory_config
 from pyams_utils.interfaces import VIEW_PERMISSION
@@ -44,8 +46,7 @@
         params = request.params
         return params.get('user_search', '').strip()
 
-    @staticmethod
-    def _get_items(request=None, start=0, length=10, limit=None, ignore_cache=False):
+    def _get_items(self, request=None, start=0, length=10, limit=None, ignore_cache=False):
         context = get_parent(request.context, ISearchFolder)
         if context is None:
             raise StopIteration
@@ -57,6 +58,11 @@
             if (order_by == RELEVANCE_ORDER) and \
                     not SearchResultsPortletSettings.has_user_query(request):
                 request.GET['order_by'] = order_by = VISIBLE_PUBLICATION_DATE_ORDER
+            renderer_settings = IPortletRendererSettings(self)
+            if IAggregatedPortletRenderer.providedBy(renderer_settings):
+                aggregates = renderer_settings.aggregates
+            else:
+                aggregates = {}
             yield from context.get_results(context, order_by,
                                            reverse=order_by != RELEVANCE_ORDER,
                                            limit=limit,
@@ -64,19 +70,20 @@
                                            length=int(length),
                                            ignore_cache=ignore_cache,
                                            get_count=True,
-                                           request=request)
+                                           request=request,
+                                           aggregates=aggregates)
 
     def get_items(self, request=None, start=0, length=10, limit=None, ignore_cache=False):
         if not (self.allow_empty_query or self.has_user_query(request)):
-            yield from iter(((), 0), )
+            yield from iter(((), 0, {}), )
         else:
             check, items = tee(
-                SearchResultsPortletSettings._get_items(request, start, length, limit,
-                                                        ignore_cache))
+                self._get_items(request, start, length, limit,
+                                ignore_cache))
             try:
                 next(check)
             except StopIteration:
-                yield from iter(((), 0), )
+                yield from iter(((), 0, {}), )
             else:
                 yield from items
 
--- a/src/pyams_content/features/search/portlet/interfaces.py	Tue Dec 01 09:19:16 2020 +0100
+++ b/src/pyams_content/features/search/portlet/interfaces.py	Mon Jan 11 16:45:22 2021 +0100
@@ -10,15 +10,24 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
+from zope.interface import Attribute, Interface
 from zope.schema import Bool
 
-from pyams_content import _
 from pyams_i18n.schema import I18nTextLineField
 from pyams_portal.interfaces import IPortletSettings
 
 
+__docformat__ = 'restructuredtext'
+
+from pyams_content import _
+
+
+class IAggregatedPortletRenderer(Interface):
+    """Search results portlet renderer with aggregations"""
+
+    aggregates = Attribute("Search results aggregates")
+
+
 class ISearchResultsPortletSettings(IPortletSettings):
     """Search results portlet settings"""
 
--- a/src/pyams_content/features/search/portlet/zmi/templates/search-preview.pt	Tue Dec 01 09:19:16 2020 +0100
+++ b/src/pyams_content/features/search/portlet/zmi/templates/search-preview.pt	Mon Jan 11 16:45:22 2021 +0100
@@ -1,6 +1,6 @@
 <div class="padding-x-5" i18n:domain="pyams_content"
 	 tal:define="settings view.settings; global count 0;
-				 (items, count) settings.get_items(request, limit=10, ignore_cache=True);">
+				 (items, count, aggregations) settings.get_items(request, limit=10, ignore_cache=True);">
 	<strong>${i18n:settings.title}</strong>
 	<div class="padding-x-10">
 		<i class="fa fa-fw fa-${'check-' if settings.allow_empty_query else ''}square-o"></i>
--- a/src/pyams_content/shared/view/__init__.py	Tue Dec 01 09:19:16 2020 +0100
+++ b/src/pyams_content/shared/view/__init__.py	Mon Jan 11 16:45:22 2021 +0100
@@ -111,8 +111,9 @@
         return list(data_types)
 
     def get_results(self, context, sort_index=None, reverse=None, limit=None,
-                    start=0, length=999, ignore_cache=False, get_count=False, request=None):
-        results, count = _MARKER, 0
+                    start=0, length=999, ignore_cache=False, get_count=False, request=None,
+                    aggregates=None):
+        results, count, aggregations = _MARKER, 0, {}
         if not ignore_cache:
             # check for cache
             views_cache = get_cache(VIEWS_CACHE_REGION, VIEWS_CACHE_NAME)
@@ -124,6 +125,7 @@
             try:
                 results = views_cache.get_value(cache_key)
                 count = views_cache.get_value(cache_key + '::count')
+                aggregations = views_cache.get_value(cache_key + '::aggregations')
             except KeyError:
                 pass
         # Execute query
@@ -136,22 +138,25 @@
             if not sort_index:
                 sort_index = self.order_by
             # Get query results
-            results, count = adapter.get_results(context,
-                                                 sort_index,
-                                                 reverse if reverse is not None else self.reversed_order,
-                                                 limit or self.limit,
-                                                 request=request)
+            results, count, aggregations = adapter.get_results(context,
+                                                               sort_index,
+                                                               reverse if reverse is not None
+                                                                   else self.reversed_order,
+                                                               limit or self.limit,
+                                                               request=request,
+                                                               aggregates=aggregates)
             count = min(count, limit or self.limit or 999)
             cache, results = tee(islice(results, start, start + length))
             if not ignore_cache:
                 intids = get_utility(IIntIds)
                 views_cache.set_value(cache_key, [intids.queryId(item) for item in cache])
                 views_cache.set_value(cache_key + '::count', count)
+                views_cache.set_value(cache_key + '::aggregations', aggregations)
                 logger.debug("Storing view items to cache key {0}".format(cache_key))
         else:
             results = CatalogResultSet(results)
             logger.debug("Retrieving view items from cache key {0}".format(cache_key))
-        return (results, count) if get_count else results
+        return (results, count, aggregations) if get_count else results
 
 
 register_content_type(WfView, shared_content=False)
@@ -215,7 +220,8 @@
                 filters &= Any(catalog['data_type'], data_types)
         return params
 
-    def get_results(self, context, sort_index, reverse, limit, request=None):
+    def get_results(self, context, sort_index, reverse, limit,
+                    request=None, aggregates=None):
         view = self.context
         catalog = get_utility(ICatalog)
         registry = get_current_registry()
@@ -235,7 +241,7 @@
         for name, adapter in sorted(registry.getAdapters((view,), IViewQueryFilterExtension),
                                     key=lambda x: x[1].weight):
             items = adapter.filter(context, items, request)
-        return unique_iter(items), total_count
+        return unique_iter(items), total_count, {}
 
 
 @subscriber(IObjectModifiedEvent, context_selector=IWfView)
--- a/src/pyams_content/shared/view/interfaces.py	Tue Dec 01 09:19:16 2020 +0100
+++ b/src/pyams_content/shared/view/interfaces.py	Mon Jan 11 16:45:22 2021 +0100
@@ -154,7 +154,8 @@
     def get_params(self, context, request=None):
         """Get static view query params"""
 
-    def get_results(self, context, sort_index, reverse, limit, request=None):
+    def get_results(self, context, sort_index, reverse, limit,
+                    request=None, aggregates=None):
         """Get tuple of limited results and total results count"""