Added global search engine
authorThierry Florac <thierry.florac@onf.fr>
Fri, 08 Dec 2017 10:41:32 +0100
changeset 304 14b2ae98f78c
parent 303 4109e6d2991b
child 305 b5967aacf4c1
Added global search engine
src/pyams_content/root/zmi/search.py
src/pyams_content/root/zmi/sites.py
src/pyams_content/root/zmi/templates/advanced-search.pt
src/pyams_content/root/zmi/templates/dashboard.pt
--- a/src/pyams_content/root/zmi/search.py	Mon Dec 04 15:36:40 2017 +0100
+++ b/src/pyams_content/root/zmi/search.py	Fri Dec 08 10:41:32 2017 +0100
@@ -16,5 +16,263 @@
 # import standard library
 
 # import interfaces
+from hypatia.interfaces import ICatalog
+from pyams_content.profile.interfaces import IAdminProfile
+from pyams_content.root import ISiteRoot
+from pyams_content.shared.common.interfaces.zmi import ISiteRootDashboardTable
+from pyams_content.zmi.interfaces import IAllContentsMenu
+from pyams_form.search import ISearchFields, SearchForm, SearchView, SearchResultsView
+from pyams_i18n.interfaces import INegotiator
+from pyams_sequence.interfaces import ISequentialIntIds
+from pyams_skin.interfaces import IInnerPage, IContentSearch, IPageHeader
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+from pyams_workflow.interfaces import IWorkflowVersions
+from pyams_zmi.layer import IAdminLayer
+from z3c.table.interfaces import IValues
+from zope.intid import IIntIds
 
 # import packages
+from hypatia.catalog import CatalogQuery
+from hypatia.query import Eq, Any, Contains, Ge, Le
+from pyams_catalog.query import CatalogResultSet
+from pyams_content.shared.common import CONTENT_TYPES
+from pyams_pagelet.interfaces import PageletCreatedEvent
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_security.schema import Principal
+from pyams_skin.page import DefaultPageHeaderAdapter
+from pyams_skin.skin import apply_skin
+from pyams_skin.table import BaseTable
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+from pyams_utils.list import unique
+from pyams_utils.registry import get_utility
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.view import AdminView
+from pyramid.view import view_config
+from z3c.form import field
+from zope.interface import implementer
+from zope.schema import Datetime, Choice, List
+
+from pyams_content import _
+
+
+#
+# Quick search adapters
+#
+
+@view_config(name='quick-search.html', context=ISiteRoot, request_type=IPyAMSLayer,
+             permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True)
+def site_root_quick_search_view(request):
+    """Site root quick search view"""
+    results = SiteRootQuickSearchResults(request.context, request)
+    if len(results.values) == 1:
+        result = results.values[0]
+        return {'status': 'redirect',
+                'location': absolute_url(result, request, 'admin')}
+    else:
+        results.update()
+        return {'status': 'info',
+                'content': {'html': results.render()}}
+
+
+@implementer(ISiteRootDashboardTable)
+class SiteRootQuickSearchResults(BaseTable):
+    """Site root quick search results table"""
+
+    title = _("Quick search results")
+
+    sortOn = None
+
+    @property
+    def data_attributes(self):
+        attributes = super(SiteRootQuickSearchResults, self).data_attributes
+        attributes['table'] = {'data-ams-datatable-sorting': '[]',
+                               'data-ams-datatable-display-length':
+                                   IAdminProfile(self.request.principal).table_page_length}
+        return attributes
+
+
+@adapter_config(context=(ISiteRoot, IPyAMSLayer, SiteRootQuickSearchResults), provides=IValues)
+class SiteRootQuickSearchValues(ContextRequestViewAdapter):
+    """Site root quick search results view values adapter"""
+
+    @property
+    def values(self):
+        intids = get_utility(IIntIds)
+        catalog = get_utility(ICatalog)
+        params = Eq(catalog['parents'], intids.register(self.context)) & \
+                 Any(catalog['content_type'], CONTENT_TYPES.keys())
+        query = self.request.params.get('query')
+        if query:
+            sequence = get_utility(ISequentialIntIds)
+            if query.startswith('+'):
+                params &= Eq(catalog['oid'], sequence.get_full_oid(query))
+            else:
+                query_params = Eq(catalog['oid'], sequence.get_full_oid(query))
+                negotiator = get_utility(INegotiator)
+                for lang in {self.request.registry.settings.get('pyramid.default_locale_name', 'en'),
+                             self.request.locale_name,
+                             negotiator.server_language} | negotiator.offered_languages:
+                    index_name = 'title:{0}'.format(lang)
+                    if index_name in catalog:
+                        index = catalog[index_name]
+                        if index.check_query(query):
+                            query_params |= Contains(index, ' and '.join((w+'*' for w in query.split())))
+                params &= query_params
+        return unique(map(lambda x: IWorkflowVersions(x).get_last_versions()[0],
+                          CatalogResultSet(CatalogQuery(catalog).query(params,
+                                                                       sort_index='modified_date',
+                                                                       reverse=True))))
+
+
+#
+# Advanced search adapters
+#
+
+@viewlet_config(name='advanced-search.menu', context=ISiteRoot, layer=IAdminLayer,
+                manager=IAllContentsMenu, permission=VIEW_SYSTEM_PERMISSION, weight=90)
+class SiteRootAdvancedSearchMenu(MenuItem):
+    """Site root advanced search menu"""
+
+    label = _("Advanced search")
+    icon_class = None
+    url = '#advanced-search.html'
+
+
+class ISiteRootAdvancedSearchFields(ISearchFields):
+    """Site root advanced search fields"""
+
+    content_type = List(title=_("Content types"),
+                        value_type=Choice(vocabulary='PyAMS content types'),
+                        required=False)
+
+    owner = Principal(title=_("Owner"),
+                      required=False)
+
+    created_after = Datetime(title=_("Created after..."),
+                             required=False)
+
+    created_before = Datetime(title=_("Created before..."),
+                              required=False)
+
+    modified_after = Datetime(title=_("Modified after..."),
+                              required=False)
+
+    modified_before = Datetime(title=_("Modified before..."),
+                               required=False)
+
+
+@template_config(template='templates/advanced-search.pt', layer=IPyAMSLayer)
+@implementer(IInnerPage)
+class SiteRootAdvancedSearchForm(SearchForm):
+    """Site root advanced search form"""
+
+    legend = _("Advanced search")
+
+    def __init__(self, context, request):
+        super(SiteRootAdvancedSearchForm, self).__init__(context, request)
+        request.registry.notify(PageletCreatedEvent(self))
+        apply_skin(self.request, 'PyAMS admin skin')
+
+    fields = field.Fields(ISiteRootAdvancedSearchFields)
+    ajax_handler = 'advanced-search-results.html'
+
+
+@adapter_config(context=(ISiteRoot, IPyAMSLayer, SiteRootAdvancedSearchForm), provides=IContentSearch)
+class SiteRootAdvancedSearchFormSearchAdapter(ContextRequestViewAdapter):
+    """Site root advanced search form search adapter"""
+
+    def get_search_results(self, data):
+        intids = get_utility(IIntIds)
+        catalog = get_utility(ICatalog)
+        params = Eq(catalog['parents'], intids.register(self.context))
+        if data.get('content_type'):
+            params &= Any(catalog['content_type'], data['content_type'])
+        else:
+            params &= Any(catalog['content_type'], CONTENT_TYPES.keys())
+        query = data.get('query')
+        if query:
+            sequence = get_utility(ISequentialIntIds)
+            if query.startswith('+'):
+                params &= Eq(catalog['oid'], sequence.get_full_oid(query))
+            else:
+                query_params = Eq(catalog['oid'], sequence.get_full_oid(query))
+                negotiator = get_utility(INegotiator)
+                for lang in {self.request.registry.settings.get('pyramid.default_locale_name', 'en'),
+                             self.request.locale_name,
+                             negotiator.server_language} | negotiator.offered_languages:
+                    index_name = 'title:{0}'.format(lang)
+                    if index_name in catalog:
+                        index = catalog[index_name]
+                        if index.check_query(query):
+                            query_params |= Contains(index, ' and '.join((w+'*' for w in query.split())))
+                params &= query_params
+        if data.get('owner'):
+            params &= Eq(catalog['role:owner'], data['owner'])
+        if data.get('created_after'):
+            params &= Ge(catalog['created_date'], data['created_after'])
+        if data.get('created_before'):
+            params &= Le(catalog['created_date'], data['created_before'])
+        if data.get('modified_after'):
+            params &= Ge(catalog['modified_date'], data['modified_after'])
+        if data.get('modified_before'):
+            params &= Le(catalog['modified_date'], data['modified_before'])
+        return unique(map(lambda x: IWorkflowVersions(x).get_last_versions()[0],
+                          CatalogResultSet(CatalogQuery(catalog).query(params,
+                                                                       sort_index='modified_date',
+                                                                       reverse=True))))
+
+
+@pagelet_config(name='advanced-search.html', context=ISiteRoot, layer=IPyAMSLayer,
+                permission=VIEW_SYSTEM_PERMISSION)
+class SiteRootAdvancedSearchView(SearchView):
+    """Site root advanced search view"""
+
+    search_form_factory = SiteRootAdvancedSearchForm
+
+
+@adapter_config(context=(ISiteRoot, IPyAMSLayer, SiteRootAdvancedSearchView), provides=IPageHeader)
+class SiteRootAdvancedSearchHeaderAdapter(DefaultPageHeaderAdapter):
+    """Site root advanced search header adapter"""
+
+    back_url = '#dashboard.html'
+    back_target = None
+
+    icon_class = 'fa fa-fw fa-search'
+
+
+@view_config(name='advanced-search-results.html', context=ISiteRoot, request_type=IPyAMSLayer,
+             permission=VIEW_SYSTEM_PERMISSION)
+@implementer(ISiteRootDashboardTable)
+class SiteRootAdvancedSearchResultsView(AdminView, SearchResultsView):
+    """Site root advanced search results view"""
+
+    title = _("Advanced search results")
+    search_form_factory = SiteRootAdvancedSearchForm
+
+    sortOn = None
+
+    def __init__(self, context, request):
+        super(SiteRootAdvancedSearchResultsView, self).__init__(context, request)
+        request.registry.notify(PageletCreatedEvent(self))
+
+    @property
+    def data_attributes(self):
+        attributes = super(SiteRootAdvancedSearchResultsView, self).data_attributes
+        attributes['table'] = {'data-ams-datatable-sorting': '[]',
+                               'data-ams-datatable-display-length':
+                                   IAdminProfile(self.request.principal).table_page_length}
+        return attributes
+
+
+@adapter_config(context=(ISiteRoot, IPyAMSLayer, SiteRootAdvancedSearchResultsView), provides=IValues)
+class SearchResultsViewValuesAdapter(ContextRequestViewAdapter):
+    """Search results view values adapter"""
+
+    @property
+    def values(self):
+        form = self.view.search_form_factory(self.context, self.request)
+        return form.get_search_results() or ()
--- a/src/pyams_content/root/zmi/sites.py	Mon Dec 04 15:36:40 2017 +0100
+++ b/src/pyams_content/root/zmi/sites.py	Fri Dec 08 10:41:32 2017 +0100
@@ -23,20 +23,25 @@
 from pyams_content.zmi.interfaces import ISiteTreeMenu, ISiteTreeTable
 from pyams_sequence.interfaces import ISequentialIdInfo
 from pyams_skin.interfaces import IInnerPage, IPageHeader
+from pyams_skin.interfaces.container import ITableWithActions
 from pyams_skin.layer import IPyAMSLayer
 from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
 from pyams_workflow.interfaces import IWorkflowPublicationInfo
 from pyams_zmi.interfaces.menu import ISiteManagementMenu
 from pyams_zmi.layer import IAdminLayer
 from z3c.table.interfaces import IColumn, IValues
+from zope.intid.interfaces import IIntIds
 
 # import packages
+from pyams_content.skin import pyams_content
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.container import ContainerView
 from pyams_skin.page import DefaultPageHeaderAdapter
 from pyams_skin.table import BaseTable, TrashColumn, ActionColumn, I18nColumn
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
+from pyams_utils.fanstatic import get_resource_path
+from pyams_utils.registry import get_utility
 from pyams_utils.url import absolute_url
 from pyams_viewlet.manager import viewletmanager_config
 from pyams_viewlet.viewlet import viewlet_config
@@ -52,7 +57,7 @@
 # Sites and blogs view
 #
 
-@viewlet_config(name='site-tree.menu', layer=IAdminLayer, context=ISiteRoot, manager=ISiteManagementMenu,
+@viewlet_config(name='site-tree.menu', context=ISiteRoot, layer=IAdminLayer, manager=ISiteManagementMenu,
                 permission=VIEW_SYSTEM_PERMISSION, weight=5)
 @viewletmanager_config(name='site-tree.menu', layer=IAdminLayer, context=ISiteRoot, provides=ISiteTreeMenu)
 @implementer(ISiteTreeMenu)
@@ -64,7 +69,7 @@
     url = '#site-tree.html'
 
 
-@implementer(IDashboardTable, ISiteTreeTable)
+@implementer(IDashboardTable, ITableWithActions, ISiteTreeTable)
 class SiteTreeTable(BaseTable):
     """Site tree table"""
 
@@ -76,8 +81,19 @@
     @property
     def data_attributes(self):
         attributes = super(SiteTreeTable, self).data_attributes
-        attributes['table'] = {'data-ams-location': absolute_url(self.context, self.request),
-                               'data-ams-delete-target': 'delete-shared-site.json'}
+        intids = get_utility(IIntIds)
+        attributes.setdefault('table', {}).update({
+            'data-ams-plugins': 'pyams_content',
+            'data-ams-plugin-pyams_content-src': get_resource_path(pyams_content),
+            'data-ams-location': absolute_url(self.context, self.request),
+            'data-ams-delete-target': 'delete-shared-site.json'
+        })
+        attributes.setdefault('tr', {}).update({
+            'id': lambda x, col: '{0}::{1}'.format(self.id, intids.queryId(x)),
+            'data-ams-location': lambda x, col: absolute_url(x.__parent__, self.request),
+            'data-ams-tree-node-id': lambda x, col: intids.queryId(x),
+            'data-ams-tree-node-parent-id': lambda x, col: intids.queryId(x.__parent__)
+        })
         return attributes
 
 
@@ -123,7 +139,7 @@
             return '--'
         else:
             try:
-                return sequence.get_short_oid()
+                return sequence.get_base_oid()
             except TypeError:
                 return '--'
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/root/zmi/templates/advanced-search.pt	Fri Dec 08 10:41:32 2017 +0100
@@ -0,0 +1,223 @@
+<div class="ams-widget" i18n:domain="pyams_content">
+	<header>
+		<span tal:condition="view.widget_icon_class | nothing"
+			  class="widget-icon"><i tal:attributes="class view.widget_icon_class"></i>
+		</span>
+		<h2 tal:content="view.legend"></h2>
+		<tal:var content="structure provider:pyams.widget_title" />
+		<tal:var content="structure provider:pyams.toolbar" />
+	</header>
+	<div class="widget-body no-padding">
+		<div tal:define="prefix provider:form_prefix"
+			 tal:replace="structure prefix">Form prefix</div>
+		<tal:var content="structure provider:content_help" />
+		<form method="post"
+			  data-async
+			  tal:attributes="id view.id;
+							  name view.name;
+							  action view.get_form_action();
+							  method view.method;
+							  enctype view.enctype;
+							  acceptCharset view.acceptCharset;
+							  accept view.accept;
+							  autocomplete view.autocomplete;
+							  class view.css_class;
+							  data-ams-data extension:object_data(view);
+							  data-ams-form-handler view.get_ajax_handler() | nothing;
+							  data-ams-form-options view.get_form_options() | nothing;
+							  data-ams-form-submit-target view.form_target | nothing;
+							  data-ams-form-download-target view.download_target | nothing;
+							  data-ams-warn-on-change view.warn_on_change;">
+			<div class="modal-viewport">
+				<fieldset>
+					<div class="widgets-prefix"
+						 tal:define="prefix provider:widgets_prefix"
+						 tal:condition="prefix"
+						 tal:content="structure prefix">Widgets prefix</div>
+					<tal:loop repeat="group view.groups">
+						<fieldset tal:define="legend group.legend"
+								  tal:omit-tag="not:legend"
+								  tal:attributes="class 'bordered' if group.bordered else None">
+							<tal:if condition="group.checkbox_switch">
+								<legend data-ams-checker-value="selected"
+										tal:condition="legend"
+										tal:attributes="class group.css_class;
+														data-ams-checker-fieldname '{0}:list'.format(group.checkbox_widget.name);
+														data-ams-checker-readonly 'readonly' if group.checkbox_widget.mode == 'display' else None;
+														data-ams-checker-marker '{0}-empty-marker'.format(group.checkbox_widget.name);
+														data-ams-checker-state group.checker_state;">
+									<label tal:content="legend">Legend</label>
+								</legend>
+							</tal:if>
+							<tal:if condition="not:group.checkbox_switch">
+								<legend tal:condition="legend"
+										tal:content="legend"
+										tal:attributes="class group.css_class;
+														data-ams-switcher-state group.switcher_state;">Legend</legend>
+							</tal:if>
+							<tal:var define="help group.help" condition="help">
+								<div class=""
+									 tal:define="html import:pyams_utils.text.text_to_html;
+												 i18n_help html(request.localizer.translate(help));"
+									 tal:content="structure i18n_help"></div>
+							</tal:var>
+							<div class="form-group" tal:define="widget view.widgets['query']">
+								<label class="control-label col-md-3">
+									<span>
+										<tal:var content="widget.label" />
+										<i class="fa fa-question-circle hint" title="Input hint"
+										   tal:define="description widget.field.description"
+										   tal:condition="description"
+										   tal:attributes="title description;
+														   data-ams-hint-html '<' in description;"></i>
+									</span>
+								</label>
+								<div class="col-md-9">
+									<label class="input"
+										   tal:attributes="class widget.widget_css_class | default;
+														   data-ams-data extension:object_data(widget);
+														   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+										<input tal:replace="structure widget.render()" />
+									</label>
+								</div>
+							</div>
+							<div class="form-group">
+								<tal:var define="widget view.widgets['owner']">
+									<label class="control-label col-md-3">
+										<span>
+											<tal:var content="widget.label" />
+											<i class="fa fa-question-circle hint" title="Input hint"
+											   tal:define="description widget.field.description"
+											   tal:condition="description"
+											   tal:attributes="title description;
+															   data-ams-hint-html '<' in description;"></i>
+										</span>
+									</label>
+									<div class="col-md-4">
+										<label class="input"
+											   tal:attributes="class widget.widget_css_class | default;
+															   data-ams-data extension:object_data(widget);
+															   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+											<input tal:replace="structure widget.render()" />
+										</label>
+									</div>
+								</tal:var>
+								<tal:var define="widget view.widgets['content_type']">
+									<label class="control-label col-md-2">
+										<span>
+											<tal:var content="widget.label" />
+											<i class="fa fa-question-circle hint" title="Input hint"
+											   tal:define="description widget.field.description"
+											   tal:condition="description"
+											   tal:attributes="title description;
+															   data-ams-hint-html '<' in description;"></i>
+										</span>
+									</label>
+									<div class="col-md-3">
+										<label class="input"
+											   tal:attributes="class widget.widget_css_class | default;
+															   data-ams-data extension:object_data(widget);
+															   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+											<input tal:replace="structure widget.render()" />
+										</label>
+									</div>
+								</tal:var>
+							</div>
+							<div class="form-group">
+								<label class="control-label col-md-3">
+									<span i18n:translate="">Created between</span>
+								</label>
+								<div class="col-md-4">
+									<label class="input"
+											tal:define="widget view.widgets['created_after']"
+										   tal:attributes="class widget.widget_css_class | default;
+														   data-ams-data extension:object_data(widget);
+														   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+										<input tal:replace="structure widget.render()" />
+									</label>
+								</div>
+								<div class="control-label col-md-1 text-align-center">
+									<i18n:var translate=""> and </i18n:var>
+								</div>
+								<div class="col-md-4">
+									<label class="input"
+											tal:define="widget view.widgets['created_before']"
+										   tal:attributes="class widget.widget_css_class | default;
+														   data-ams-data extension:object_data(widget);
+														   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+										<input tal:replace="structure widget.render()" />
+									</label>
+								</div>
+							</div>
+							<div class="form-group">
+								<label class="control-label col-md-3">
+									<span i18n:translate="">Modified between</span>
+								</label>
+								<div class="col-md-4">
+									<label class="input"
+											tal:define="widget view.widgets['modified_after']"
+										   tal:attributes="class widget.widget_css_class | default;
+														   data-ams-data extension:object_data(widget);
+														   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+										<input tal:replace="structure widget.render()" />
+									</label>
+								</div>
+								<div class="control-label col-md-1 text-align-center">
+									<i18n:var translate=""> and </i18n:var>
+								</div>
+								<div class="col-md-4">
+									<label class="input"
+											tal:define="widget view.widgets['modified_before']"
+										   tal:attributes="class widget.widget_css_class | default;
+														   data-ams-data extension:object_data(widget);
+														   data-ams-form-validator view.get_widget_callback(widget.field.getName())">
+										<input tal:replace="structure widget.render()" />
+									</label>
+								</div>
+							</div>
+						</fieldset>
+					</tal:loop>
+					<div class="widgets-suffix"
+						 tal:define="suffix provider:widgets_suffix"
+						 tal:condition="suffix"
+						 tal:content="structure suffix">Widgets suffix</div>
+					<div class="subforms"
+						 tal:condition="view.subforms">
+						<fieldset tal:define="title view.subforms_legend"
+								  tal:omit-tag="not:title">
+							<legend tal:condition="title" tal:content="title" i18n:translate="">Title</legend>
+							<tal:loop repeat="subform view.subforms">
+								<tal:var replace="structure subform.render()" />
+							</tal:loop>
+						</fieldset>
+					</div>
+					<div class="tabforms"
+						 tal:condition="view.tabforms">
+						<ul class="nav nav-tabs">
+							<li tal:repeat="tabform view.tabforms"
+								tal:attributes="class 'small {active} {errors}'.format(active='active' if repeat['tabform'].start() else '',
+																					   errors='state-error' if tabform.widgets.errors else '')">
+								<a data-toggle="tab"
+								   tal:attributes="href string:#${tabform.id}"
+								   tal:content="tabform.tab_label" i18n:translate="">Tab label</a>
+							</li>
+						</ul>
+						<div class="tab-content">
+							<div class="tab-pane fade in"
+								 tal:repeat="tabform view.tabforms"
+								 tal:attributes="id tabform.id;
+												 class 'tab-pane {active} fade in'.format(active='active' if repeat['tabform'].start() else '');"
+								 tal:content="structure tabform.render()"></div>
+						</div>
+					</div>
+				</fieldset>
+			</div>
+			<footer>
+				<button tal:repeat="action view.actions.values()"
+						tal:replace="structure action.render()">Action</button>
+			</footer>
+		</form>
+		<div tal:define="prefix provider:form_suffix"
+			 tal:replace="structure prefix">Form suffix</div>
+	</div>
+</div>
--- a/src/pyams_content/root/zmi/templates/dashboard.pt	Mon Dec 04 15:36:40 2017 +0100
+++ b/src/pyams_content/root/zmi/templates/dashboard.pt	Fri Dec 08 10:41:32 2017 +0100
@@ -13,7 +13,30 @@
 		<tal:var content="structure provider:pyams.toolbar" />
 	</header>
 	<div class="widget-body">
-		<tal:var define="global dashboard_length 0">
+		<div class="ams-widget">
+			<header>
+				<h2 i18n:translate="">SEARCH - Between all contents</h2>
+			</header>
+			<div class="padding-10 padding-bottom-0">
+				<form class="ams-form clearfix margin-bottom-10" method="post" action="quick-search.html"
+					  data-async data-ams-form-submit-target="#search_results">
+					<div class="form-group">
+						<div class="col-md-6">
+							<label class="input">
+								<button type="submit" class="icon-append fa fa-fw fa-search no-border no-padding"
+										data-ams-form-hide-loading="true"></button>
+								<input type="text" name="query" placeholder="Quick search..." i18n:attributes="placeholder" />
+							</label>
+						</div>
+						<div class="col-md-6">
+							<a class="nowrap btn-sm col-md-2" href="#advanced-search.html"
+							   i18n:translate="">Advanced search...</a>
+						</div>
+					</div>
+				</form>
+			</div>
+		</div>
+		<div id="search_results" tal:define="global dashboard_length 0">
 			<tal:loop repeat="table view.tables">
 				<tal:if condition="table.values">
 					<tal:var content="structure table.render()" />
@@ -23,6 +46,6 @@
 			<div tal:condition="not:dashboard_length" class="alert alert-info" i18n:translate="">
 				You are not actually concerned by any content.
 			</div>
-		</tal:var>
+		</div>
 	</div>
 </div>