--- a/src/pyams_default_theme/features/search/portlet/__init__.py Tue Sep 17 12:00:26 2019 +0200
+++ b/src/pyams_default_theme/features/search/portlet/__init__.py Wed Sep 25 10:05:57 2019 +0200
@@ -10,8 +10,6 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
-
from persistent import Persistent
from zope.container.contained import Contained
from zope.interface import Interface, implementer
@@ -21,9 +19,11 @@
from pyams_content.features.search import ISearchFolder
from pyams_content.features.search.portlet import ISearchResultsPortletSettings
from pyams_content.shared.common import IWfSharedContent
-from pyams_default_theme.features.search.portlet.interfaces import ISearchResultHeader, ISearchResultRenderer, \
- ISearchResultTarget, ISearchResultTitle, ISearchResultsPortletDefaultRendererSettings
-from pyams_default_theme.interfaces import ISearchResultsView
+from pyams_default_theme.features.search.portlet.interfaces import ISearchResultHeader, \
+ ISearchResultRenderer, ISearchResultTarget, ISearchResultTitle, \
+ ISearchResultsPortletDefaultRendererSettings, ISearchResultsPortletPanelsRendererSettings, \
+ ISearchResultsPortletRendererBaseSettings
+from pyams_default_theme.interfaces import ISearchResultsPanelView, ISearchResultsView
from pyams_default_theme.shared.common.interfaces import ISharedContentHeadViewletManager
from pyams_i18n.interfaces import II18n
from pyams_portal.interfaces import IPortalContext, IPortletRenderer
@@ -36,6 +36,9 @@
from pyams_utils.url import canonical_url, relative_url
from pyams_viewlet.viewlet import ViewContentProvider, Viewlet, viewlet_config
+
+__docformat__ = 'restructuredtext'
+
from pyams_default_theme import _
@@ -43,45 +46,43 @@
# Search folder custom head specificities renderer
#
-@viewlet_config(name='search-folder-head', context=ISearchFolder, layer=IPyAMSUserLayer, view=Interface,
- manager=ISharedContentHeadViewletManager, weight=1)
+@viewlet_config(name='search-folder-head', context=ISearchFolder, layer=IPyAMSUserLayer,
+ view=Interface, manager=ISharedContentHeadViewletManager, weight=1)
@template_config(template='templates/folder-head-specificities.pt', layer=IPyAMSUserLayer)
class SearchFolderHeadViewlet(Viewlet):
"""Search folder head specificities viewlet"""
#
-# Search results portlet renderers
+# Search results portlet base settings and renderers
#
-@factory_config(provided=ISearchResultsPortletDefaultRendererSettings)
-class SearchResultsPortletDefaultRendererSettings(Persistent, Contained):
- """Search results portlet default renderer settings"""
+@implementer(ISearchResultsPortletRendererBaseSettings)
+class SearchResultsPortletRendererBaseSettings(Persistent, Contained):
+ """Search results portlet base renderer settings"""
- display_results_count = FieldProperty(ISearchResultsPortletDefaultRendererSettings['display_results_count'])
- allow_sorting = FieldProperty(ISearchResultsPortletDefaultRendererSettings['allow_sorting'])
- allow_pagination = FieldProperty(ISearchResultsPortletDefaultRendererSettings['allow_pagination'])
+ display_results_count = FieldProperty(
+ ISearchResultsPortletRendererBaseSettings['display_results_count'])
+ allow_sorting = FieldProperty(ISearchResultsPortletRendererBaseSettings['allow_sorting'])
+ allow_pagination = FieldProperty(ISearchResultsPortletRendererBaseSettings['allow_pagination'])
-@adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, ISearchResultsPortletSettings),
- provides=IPortletRenderer)
-@template_config(template='templates/search-results.pt', layer=IPyAMSLayer)
-@implementer(ISearchResultsView)
-class SearchResultsPortletDefaultRenderer(PortletRenderer):
- """Search results portlet default renderer"""
+class SearchResultsPortletBaseRenderer(PortletRenderer):
+ """Search results portlet base renderer"""
- label = _("Default search results")
-
- settings_interface = ISearchResultsPortletDefaultRendererSettings
+ default_page_length = 10
def update(self):
settings = self.renderer_settings
if not settings.allow_pagination:
self.request.GET['length'] = '999'
- super(SearchResultsPortletDefaultRenderer, self).update()
+ elif 'length' not in self.request.params:
+ self.request.GET['length'] = str(self.default_page_length)
+ super(SearchResultsPortletBaseRenderer, self).update()
def render_item(self, item):
- renderer = self.request.registry.queryMultiAdapter((item, self.request, self), ISearchResultRenderer)
+ renderer = self.request.registry.queryMultiAdapter((item, self.request, self),
+ ISearchResultRenderer)
if renderer is not None:
renderer.update()
return renderer.render()
@@ -89,24 +90,79 @@
return ''
+#
+# Search results portlet default renderer
+#
+
+@factory_config(provided=ISearchResultsPortletDefaultRendererSettings)
+class SearchResultsPortletDefaultRendererSettings(SearchResultsPortletRendererBaseSettings):
+ """Search results portlet default renderer settings"""
+
+
+@adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, ISearchResultsPortletSettings),
+ provides=IPortletRenderer)
+@template_config(template='templates/search-results.pt', layer=IPyAMSLayer)
+@implementer(ISearchResultsView)
+class SearchResultsPortletDefaultRenderer(SearchResultsPortletBaseRenderer):
+ """Search results portlet default renderer"""
+
+ label = _("Default search results")
+
+ settings_interface = ISearchResultsPortletDefaultRendererSettings
+
+
+#
+# Search results portlet panels renderer
+#
+
+@factory_config(provided=ISearchResultsPortletPanelsRendererSettings)
+class SearchResultsPortletPanelsRendererSettings(SearchResultsPortletRendererBaseSettings):
+ """Search results portlet panel renderer settings"""
+
+ button_title = FieldProperty(ISearchResultsPortletPanelsRendererSettings['button_title'])
+
+
+@adapter_config(name='panels',
+ context=(IPortalContext, IPyAMSLayer, Interface, ISearchResultsPortletSettings),
+ provides=IPortletRenderer)
+@template_config(template='templates/search-panels.pt', layer=IPyAMSLayer)
+@implementer(ISearchResultsPanelView)
+class SearchResultsPortletPanelsRenderer(SearchResultsPortletBaseRenderer):
+ """Search results portlet panels renderer"""
+
+ label = _("Paneled search results")
+
+ settings_interface = ISearchResultsPortletPanelsRendererSettings
+ default_page_length = 9
+
+ weight = 20
+
+
+#
+# Search results adapters
+#
+
@adapter_config(context=(ILocation, IPyAMSUserLayer, ISearchResultsView), provides=IBreadcrumbs)
class BreadcrumbsAdapter(NullAdapter):
"""Disable breadcrumbs in search results view"""
-@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer, ISearchResultsView), provides=ISearchResultTitle)
+@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer, ISearchResultsView),
+ provides=ISearchResultTitle)
def shared_content_result_title_adapter(context, request, view):
"""Shared content result title adapter"""
return II18n(context).query_attribute('title', request=request)
-@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer, ISearchResultsView), provides=ISearchResultHeader)
+@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer, ISearchResultsView),
+ provides=ISearchResultHeader)
def shared_content_result_header_adapter(context, request, view):
"""Shared content result header adapter"""
return II18n(context).query_attribute('header', request=request)
-@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer, ISearchResultsView), provides=ISearchResultTarget)
+@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer, ISearchResultsView),
+ provides=ISearchResultTarget)
def shared_content_result_target_adapter(context, request, view):
"""Shared content result target URL adapter"""
if view.settings.force_canonical_url:
@@ -115,7 +171,8 @@
return relative_url(context, request)
-@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer, ISearchResultsView), provides=ISearchResultRenderer)
+@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer, ISearchResultsView),
+ provides=ISearchResultRenderer)
@template_config(template='templates/search-result.pt', layer=IPyAMSUserLayer)
@implementer(ISearchResultsView)
class WfSharedContentSearchResultRenderer(ViewContentProvider):
@@ -123,12 +180,22 @@
@property
def url(self):
- return self.request.registry.queryMultiAdapter((self.context, self.request, self.view), ISearchResultTarget)
+ return self.request.registry.queryMultiAdapter((self.context, self.request, self.view),
+ ISearchResultTarget)
@property
def title(self):
- return self.request.registry.queryMultiAdapter((self.context, self.request, self.view), ISearchResultTitle)
+ return self.request.registry.queryMultiAdapter((self.context, self.request, self.view),
+ ISearchResultTitle)
@property
def header(self):
- return self.request.registry.queryMultiAdapter((self.context, self.request, self.view), ISearchResultHeader)
+ return self.request.registry.queryMultiAdapter((self.context, self.request, self.view),
+ ISearchResultHeader)
+
+
+@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer, ISearchResultsPanelView),
+ provides=ISearchResultRenderer)
+@template_config(template='templates/search-panel.pt', layer=IPyAMSUserLayer)
+class WfSharedContentSearchResultPanelRenderer(WfSharedContentSearchResultRenderer):
+ """Shared content search result panel renderer"""
--- a/src/pyams_default_theme/features/search/portlet/interfaces.py Tue Sep 17 12:00:26 2019 +0200
+++ b/src/pyams_default_theme/features/search/portlet/interfaces.py Wed Sep 25 10:05:57 2019 +0200
@@ -9,6 +9,8 @@
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
+from pyams_i18n.schema import I18nTextLineField
+
__docformat__ = 'restructuredtext'
@@ -39,8 +41,8 @@
header = Attribute("Search result header")
-class ISearchResultsPortletDefaultRendererSettings(Interface):
- """Search results portlet default renderer settings interface"""
+class ISearchResultsPortletRendererBaseSettings(Interface):
+ """Search results portlet renderer base settings interface"""
display_results_count = Bool(title=_("Display results count?"),
description=_("If 'no', results count will not be displayed"),
@@ -56,3 +58,18 @@
description=_("If 'no', results will not be paginated"),
required=True,
default=True)
+
+
+class ISearchResultsPortletDefaultRendererSettings(ISearchResultsPortletRendererBaseSettings):
+ """Search results portlet default renderer settings interface"""
+
+
+class ISearchResultsPortletPanelsRendererSettings(ISearchResultsPortletRendererBaseSettings):
+ """Search results portlet panels renderer settings interface"""
+
+ button_title = I18nTextLineField(title=_("Button's title"),
+ description=_("Navigation button's title is normally defined "
+ "based on target's content type; you can "
+ "override this label by giving a custom title "
+ "here"),
+ required=False)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/search/portlet/templates/search-panel.pt Wed Sep 25 10:05:57 2019 +0200
@@ -0,0 +1,33 @@
+<tal:var define="target view.url;
+ renderer_settings view.view.renderer_settings;"
+ i18n:domain="pyams_default_theme">
+ <div class="thumbnail hidden-xs"
+ tal:define="illustration tales:pyams_illustration(context)"
+ tal:condition="illustration">
+ <a href="${target}">
+ <tal:if define="image i18n:illustration.data;
+ alt i18n:illustration.alt_title;"
+ condition="image">
+ ${structure:tales:picture(image, lg_thumb='pano', lg_width=3, md_thumb='pano', md_width=3, sm_thumb='pano',
+ sm_width=4, xs_thumb='pano', xs_width=0, alt=alt, css_class='result_media')}
+ </tal:if>
+ </a>
+ </div>
+ <div tal:define="button_title i18n:renderer_settings.button_title">
+ <a href="${target}">
+ <h3>${view.title}</h3>
+ </a>
+ <div class="header"
+ tal:define="header view.header"
+ tal:condition="header">
+ ${structure:tales:html(header)}
+ </div>
+ <div class="action"
+ tal:condition="button_title">
+ <a class="btn btn-default"
+ href="${target}">
+ <span i18n:translate="">${button_title}</span>
+ </a>
+ </div>
+ </div>
+</tal:var>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/search/portlet/templates/search-panels.pt Wed Sep 25 10:05:57 2019 +0200
@@ -0,0 +1,105 @@
+<div tal:define="settings view.settings;
+ renderer_settings view.renderer_settings;
+ start request.params.get('start', 0);
+ length request.params.get('length', 9);"
+ tal:condition="settings.allow_empty_query or settings.has_user_query(request)"
+ i18n:domain="pyams_default_theme">
+ <h2>${i18n:settings.title}</h2>
+ <form action="${tales:absolute_url(context)}"
+ id="search-results">
+ <input type="hidden" name="user_search" value="${request.params.get('user_search')}" />
+ <input type="hidden" name="tag" value="${request.params.get('tag')}" />
+ <input type="hidden" name="order_by" value="${request.params.get('order_by') or context.order_by}" />
+ <input type="hidden" name="start" value="${start}" />
+ <input type="hidden" name="length" value="${length}" />
+ </form>
+ <div class="search-results"
+ tal:define="(items, count) settings.get_items(request, start, length)">
+ <header>
+ <div tal:condition="renderer_settings.display_results_count">
+ <tal:if condition="count" i18n:translate="">
+ <i18n:var name="count">${count}</i18n:var> result(s) found
+ </tal:if>
+ <tal:if condition="not:count" i18n:translate="">
+ No result found!
+ </tal:if>
+ </div>
+ <div tal:condition="count and renderer_settings.allow_sorting">
+ <select class="form-control"
+ data-ams-change-handler="PyAMS_default.search.updateSort"
+ tal:define="order_by request.params.get('order_by') or context.order_by">
+ <option value="relevance"
+ selected="${'selected' if order_by == 'relevance' else None}"
+ i18n:translate="">Sort by relevance</option>
+ <option value="visible_publication_date"
+ selected="${'selected' if order_by == 'visible_publication_date' else None}"
+ i18n:translate="">Sort by publication date</option>
+ <option value="modified_date"
+ selected="${'selected' if order_by == 'modified_date' else None}"
+ i18n:translate="">Sort by last modification date</option>
+ </select>
+ </div>
+ <div tal:condition="count and renderer_settings.allow_pagination">
+ <span i18n:translate="">Page length:</span>
+ <select class="form-control"
+ data-ams-change-handler="PyAMS_default.search.updatePageLength"
+ tal:define="length request.params.get('length', 9)">
+ <option tal:repeat="value ('9', '21', '45')"
+ value="${value}"
+ selected="${'selected' if value == length else None}"
+ i18n:translate="">${value}</option>
+ </select>
+ </div>
+ </header>
+ <hr />
+ <div class="summary">
+ <tal:loop repeat="item items">
+ <div class="result col-md-4 col-sm-6">
+ ${structure:view.render_item(item)}
+ </div>
+ <div class="clearfix visible-md-block visible-lg-block"
+ tal:condition="not:repeat['item'].number % 3"></div>
+ <div class="clearfix visible-sm-block"
+ tal:condition="not:repeat['item'].number % 2"></div>
+ </tal:loop>
+ </div>
+ <div class="clearfix"></div>
+ <div class="col-md-12 text-center">
+ <nav role="navigation"
+ aria-label="Pagination" i18n:attributes="aria-label">
+ <ol class="pagination"
+ tal:define="(current, total) settings.get_pages(start, length, count)"
+ data-ams-current-page="${current}">
+ <tal:if condition="renderer_settings.allow_pagination and (total > 1)">
+ <tal:if condition="current > 1">
+ <li class="prev">
+ <a href="#" i18n:translate=""
+ data-ams-click-handler="PyAMS_default.search.previousPage">Previous page</a>
+ </li>
+ </tal:if>
+ <li tal:repeat="page range(current)">
+ <a tal:condition="current != page+1"
+ href="#"
+ data-ams-click-handler="PyAMS_default.search.gotoPage">${page+1}</a>
+ <span tal:condition="current == page+1"
+ class="current">${page+1}</span>
+ </li>
+ <tal:if condition="current < total">
+ <li tal:condition="current < total-1">
+ <a class="disabled">…</a>
+ </li>
+ <li>
+ <a href="#"
+ data-ams-click-handler="PyAMS_default.search.gotoPage">${total}</a>
+ </li>
+ <li class="next">
+ <a href="#" i18n:translate=""
+ data-ams-click-handler="PyAMS_default.search.nextPage">Next page</a>
+ </li>
+ </tal:if>
+ </tal:if>
+ </ol>
+ </nav>
+ </div>
+ </div>
+</div>
--- a/src/pyams_default_theme/features/search/portlet/templates/search-result.pt Tue Sep 17 12:00:26 2019 +0200
+++ b/src/pyams_default_theme/features/search/portlet/templates/search-result.pt Wed Sep 25 10:05:57 2019 +0200
@@ -23,4 +23,4 @@
${structure:tales:html(header)}
</div>
</div>
-</tal:var>
+</tal:var>
\ No newline at end of file
--- a/src/pyams_default_theme/features/search/portlet/templates/search-results.pt Tue Sep 17 12:00:26 2019 +0200
+++ b/src/pyams_default_theme/features/search/portlet/templates/search-results.pt Wed Sep 25 10:05:57 2019 +0200
@@ -1,5 +1,7 @@
<div tal:define="settings view.settings;
- renderer_settings view.renderer_settings;"
+ renderer_settings view.renderer_settings;
+ start request.params.get('start', 0);
+ length request.params.get('length', 10);"
tal:condition="settings.allow_empty_query or settings.has_user_query(request)"
i18n:domain="pyams_default_theme">
<h2>${i18n:settings.title}</h2>
@@ -8,11 +10,11 @@
<input type="hidden" name="user_search" value="${request.params.get('user_search')}" />
<input type="hidden" name="tag" value="${request.params.get('tag')}" />
<input type="hidden" name="order_by" value="${request.params.get('order_by') or context.order_by}" />
- <input type="hidden" name="start" value="${request.params.get('start', 0)}" />
- <input type="hidden" name="length" value="${request.params.get('length', 10)}" />
+ <input type="hidden" name="start" value="${start}" />
+ <input type="hidden" name="length" value="${length}" />
</form>
<div class="search-results"
- tal:define="(items, count) settings.get_items(request)">
+ tal:define="(items, count) settings.get_items(request, start, length)">
<header>
<div tal:condition="renderer_settings.display_results_count">
<tal:if condition="count" i18n:translate="">
@@ -59,7 +61,7 @@
<nav role="navigation"
aria-label="Pagination" i18n:attributes="aria-label">
<ol class="pagination"
- tal:define="(current, total) settings.get_pages(request, count)"
+ tal:define="(current, total) settings.get_pages(start, length, count)"
data-ams-current-page="${current}">
<tal:if condition="renderer_settings.allow_pagination and (total > 1)">
<tal:if condition="current > 1">
--- a/src/pyams_default_theme/interfaces.py Tue Sep 17 12:00:26 2019 +0200
+++ b/src/pyams_default_theme/interfaces.py Wed Sep 25 10:05:57 2019 +0200
@@ -57,3 +57,7 @@
class ISearchResultsView(Interface):
"""Search results view marker interface"""
+
+
+class ISearchResultsPanelView(ISearchResultsView):
+ """Search results panel view marker interface"""