# HG changeset patch # User Thierry Florac # Date 1595117517 -7200 # Node ID e48a3850c17a47fd09ed94d48f24b018798bc622 # Parent 838fd9ca54fbe51a643a4bf9b01bcc4abe567a48 Added adapters to advanced search forms to be able to use Elasticsearch in back-office diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/locales/fr/LC_MESSAGES/pyams_content_es.mo Binary file src/pyams_content_es/locales/fr/LC_MESSAGES/pyams_content_es.mo has changed diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/locales/fr/LC_MESSAGES/pyams_content_es.po --- a/src/pyams_content_es/locales/fr/LC_MESSAGES/pyams_content_es.po Sat Jul 18 19:37:58 2020 +0200 +++ b/src/pyams_content_es/locales/fr/LC_MESSAGES/pyams_content_es.po Sun Jul 19 02:11:57 2020 +0200 @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE 1.0\n" -"POT-Creation-Date: 2019-03-01 13:28+0100\n" +"POT-Creation-Date: 2020-07-18 20:14+0200\n" "PO-Revision-Date: 2016-04-21 18:26+0200\n" "Last-Translator: Thierry Florac \n" "Language-Team: French\n" @@ -24,53 +24,53 @@ msgid "Name of ZODB connection defining indexer connection" msgstr "Nom de la ZODB définissant les paramètres de connexion" -#: src/pyams_content_es/zmi/db.py:53 +#: src/pyams_content_es/zmi/db.py:52 msgid "Check index contents..." msgstr "Vérifier le contenu de l'index" -#: src/pyams_content_es/zmi/db.py:79 +#: src/pyams_content_es/zmi/db.py:78 #: src/pyams_content_es/zmi/templates/index-ok.pt:9 msgid "Check index contents" msgstr "Vérifier le contenu de l'index" -#: src/pyams_content_es/zmi/db.py:63 src/pyams_content_es/zmi/__init__.py:82 +#: src/pyams_content_es/zmi/db.py:62 src/pyams_content_es/zmi/__init__.py:81 #: src/pyams_content_es/zmi/templates/index-ok.pt:19 #: src/pyams_content_es/zmi/templates/index-updater.pt:31 msgid "Close" msgstr "Fermer" -#: src/pyams_content_es/zmi/db.py:64 +#: src/pyams_content_es/zmi/db.py:63 #: src/pyams_content_es/zmi/templates/index-ok.pt:24 msgid "Check index" msgstr "Vérifier l'index" -#: src/pyams_content_es/zmi/db.py:185 +#: src/pyams_content_es/zmi/db.py:184 msgid "Requested contents have been re-indexed!" msgstr "Les contenus indiqués ont été ré-indexés !" -#: src/pyams_content_es/zmi/db.py:130 +#: src/pyams_content_es/zmi/db.py:129 #, python-format msgid "Loading index data ({})..." msgstr "Chargement de l'index ({})..." -#: src/pyams_content_es/zmi/db.py:139 +#: src/pyams_content_es/zmi/db.py:138 #, python-format msgid "Loading database contents ({})..." msgstr "Chargement des contenus ({})..." -#: src/pyams_content_es/zmi/__init__.py:53 +#: src/pyams_content_es/zmi/__init__.py:52 msgid "Update content indexer properties" msgstr "Propriétés de l'indexeur de contenus" -#: src/pyams_content_es/zmi/__init__.py:72 +#: src/pyams_content_es/zmi/__init__.py:71 msgid "Test process connection..." msgstr "Tester la connexion" -#: src/pyams_content_es/zmi/__init__.py:97 +#: src/pyams_content_es/zmi/__init__.py:96 msgid "Test content indexer process connection" msgstr "Test de la connexion au processus d'indexation" -#: src/pyams_content_es/zmi/__init__.py:83 +#: src/pyams_content_es/zmi/__init__.py:82 msgid "Test connection" msgstr "Tester la connexion" @@ -85,3 +85,64 @@ #: src/pyams_content_es/zmi/templates/index-updater.pt:35 msgid "Update index" msgstr "Mettre l'index à jour" + +#: src/pyams_content_es/root/zmi/search.py:47 +#: src/pyams_content_es/shared/zmi/search.py:50 +msgid "Search query" +msgstr "Texte recherché" + +#: src/pyams_content_es/root/zmi/search.py:48 +#: src/pyams_content_es/shared/zmi/search.py:51 +msgid "" +"This query will applies only on title, short name, header and description; " +"if fulltext search option is selected, this will activate search on all " +"paragraphs, including attachments" +msgstr "" +"La requête s'applique par défaut uniquement au titre, au titre court, au chapô " +"et à la description ; si l'option \"full-text\" est sélectionnée, cela activera " +"également la recherche dans le corps des paragraphes, y compris les pièces jointes" + +#: src/pyams_content_es/root/zmi/search.py:54 +#: src/pyams_content_es/shared/zmi/search.py:57 +msgid "Fulltext search" +msgstr "Recherche full-text" + +#: src/pyams_content_es/root/zmi/search.py:55 +#: src/pyams_content_es/shared/zmi/search.py:58 +msgid "Search terms in fulltext mode instead of only on contents titles" +msgstr "Lancer la recherche en mode full-text et non uniquement sur les titres et les chapôs" + +#: src/pyams_content_es/root/zmi/templates/advanced-search.pt:137 +#: src/pyams_content_es/shared/zmi/templates/advanced-search.pt:160 +msgid "Created between" +msgstr "Créé entre le" + +#: src/pyams_content_es/root/zmi/templates/advanced-search.pt:149 +#: src/pyams_content_es/root/zmi/templates/advanced-search.pt:175 +#: src/pyams_content_es/shared/zmi/templates/advanced-search.pt:172 +#: src/pyams_content_es/shared/zmi/templates/advanced-search.pt:198 +msgid "and" +msgstr "et le" + +#: src/pyams_content_es/root/zmi/templates/advanced-search.pt:163 +#: src/pyams_content_es/shared/zmi/templates/advanced-search.pt:186 +msgid "Modified between" +msgstr "Modifié entre le" + +#: src/pyams_content_es/root/zmi/templates/advanced-search.pt:188 +msgid "Tags" +msgstr "Tags" + +#: src/pyams_content_es/root/zmi/templates/advanced-search.pt:195 +msgid "Collections" +msgstr "Collections" + +#: src/pyams_content_es/root/zmi/templates/advanced-search.pt:211 +#: src/pyams_content_es/shared/zmi/templates/advanced-search.pt:230 +msgid "Title" +msgstr "Titre" + +#: src/pyams_content_es/root/zmi/templates/advanced-search.pt:225 +#: src/pyams_content_es/shared/zmi/templates/advanced-search.pt:244 +msgid "Tab label" +msgstr "Libellé" diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/locales/pyams_content_es.pot --- a/src/pyams_content_es/locales/pyams_content_es.pot Sat Jul 18 19:37:58 2020 +0200 +++ b/src/pyams_content_es/locales/pyams_content_es.pot Sun Jul 19 02:11:57 2020 +0200 @@ -1,12 +1,12 @@ # # SOME DESCRIPTIVE TITLE # This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , 2019. +# FIRST AUTHOR , 2020. #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE 1.0\n" -"POT-Creation-Date: 2019-03-01 13:28+0100\n" +"POT-Creation-Date: 2020-07-18 20:14+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" @@ -24,54 +24,54 @@ msgid "Name of ZODB connection defining indexer connection" msgstr "" -#: ./src/pyams_content_es/zmi/db.py:53 +#: ./src/pyams_content_es/zmi/db.py:52 msgid "Check index contents..." msgstr "" -#: ./src/pyams_content_es/zmi/db.py:79 +#: ./src/pyams_content_es/zmi/db.py:78 #: ./src/pyams_content_es/zmi/templates/index-ok.pt:9 msgid "Check index contents" msgstr "" -#: ./src/pyams_content_es/zmi/db.py:63 -#: ./src/pyams_content_es/zmi/__init__.py:82 +#: ./src/pyams_content_es/zmi/db.py:62 +#: ./src/pyams_content_es/zmi/__init__.py:81 #: ./src/pyams_content_es/zmi/templates/index-ok.pt:19 #: ./src/pyams_content_es/zmi/templates/index-updater.pt:31 msgid "Close" msgstr "" -#: ./src/pyams_content_es/zmi/db.py:64 +#: ./src/pyams_content_es/zmi/db.py:63 #: ./src/pyams_content_es/zmi/templates/index-ok.pt:24 msgid "Check index" msgstr "" -#: ./src/pyams_content_es/zmi/db.py:185 +#: ./src/pyams_content_es/zmi/db.py:184 msgid "Requested contents have been re-indexed!" msgstr "" -#: ./src/pyams_content_es/zmi/db.py:130 +#: ./src/pyams_content_es/zmi/db.py:129 #, python-format msgid "Loading index data ({})..." msgstr "" -#: ./src/pyams_content_es/zmi/db.py:139 +#: ./src/pyams_content_es/zmi/db.py:138 #, python-format msgid "Loading database contents ({})..." msgstr "" -#: ./src/pyams_content_es/zmi/__init__.py:53 +#: ./src/pyams_content_es/zmi/__init__.py:52 msgid "Update content indexer properties" msgstr "" -#: ./src/pyams_content_es/zmi/__init__.py:72 +#: ./src/pyams_content_es/zmi/__init__.py:71 msgid "Test process connection..." msgstr "" -#: ./src/pyams_content_es/zmi/__init__.py:97 +#: ./src/pyams_content_es/zmi/__init__.py:96 msgid "Test content indexer process connection" msgstr "" -#: ./src/pyams_content_es/zmi/__init__.py:83 +#: ./src/pyams_content_es/zmi/__init__.py:82 msgid "Test connection" msgstr "" @@ -86,3 +86,61 @@ #: ./src/pyams_content_es/zmi/templates/index-updater.pt:35 msgid "Update index" msgstr "" + +#: ./src/pyams_content_es/root/zmi/search.py:47 +#: ./src/pyams_content_es/shared/zmi/search.py:50 +msgid "Search query" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/search.py:48 +#: ./src/pyams_content_es/shared/zmi/search.py:51 +msgid "" +"This query will applies only on title, short name, header and description; if" +" fulltext search option is selected, this will activate search on all " +"paragraphs, including attachments" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/search.py:54 +#: ./src/pyams_content_es/shared/zmi/search.py:57 +msgid "Fulltext search" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/search.py:55 +#: ./src/pyams_content_es/shared/zmi/search.py:58 +msgid "Search terms in fulltext mode instead of only on contents titles" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/templates/advanced-search.pt:137 +#: ./src/pyams_content_es/shared/zmi/templates/advanced-search.pt:160 +msgid "Created between" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/templates/advanced-search.pt:149 +#: ./src/pyams_content_es/root/zmi/templates/advanced-search.pt:175 +#: ./src/pyams_content_es/shared/zmi/templates/advanced-search.pt:172 +#: ./src/pyams_content_es/shared/zmi/templates/advanced-search.pt:198 +msgid "and" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/templates/advanced-search.pt:163 +#: ./src/pyams_content_es/shared/zmi/templates/advanced-search.pt:186 +msgid "Modified between" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/templates/advanced-search.pt:188 +msgid "Tags" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/templates/advanced-search.pt:195 +msgid "Collections" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/templates/advanced-search.pt:211 +#: ./src/pyams_content_es/shared/zmi/templates/advanced-search.pt:230 +msgid "Title" +msgstr "" + +#: ./src/pyams_content_es/root/zmi/templates/advanced-search.pt:225 +#: ./src/pyams_content_es/shared/zmi/templates/advanced-search.pt:244 +msgid "Tab label" +msgstr "" diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/root/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content_es/root/__init__.py Sun Jul 19 02:11:57 2020 +0200 @@ -0,0 +1,19 @@ +# +# Copyright (c) 2015-2020 Thierry Florac +# 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. +# + +"""PyAMS_*** module + +""" + +__docformat__ = 'restructuredtext' + + diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/root/zmi/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content_es/root/zmi/__init__.py Sun Jul 19 02:11:57 2020 +0200 @@ -0,0 +1,19 @@ +# +# Copyright (c) 2015-2020 Thierry Florac +# 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. +# + +"""PyAMS_*** module + +""" + +__docformat__ = 'restructuredtext' + + diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/root/zmi/search.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content_es/root/zmi/search.py Sun Jul 19 02:11:57 2020 +0200 @@ -0,0 +1,154 @@ +# +# Copyright (c) 2015-2020 Thierry Florac +# 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. +# + +"""PyAMS_content_es.root.zmi.search module + +""" +from elasticsearch_dsl import Q, Search +from pyramid_es import get_client +from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget +from zope.intid import IIntIds +from zope.schema import Bool, TextLine + +from pyams_catalog.query import CatalogResultSet +from pyams_content.root import ISiteRoot +from pyams_content.root.zmi.search import ISiteRootAdvancedSearchFields, \ + SiteRootAdvancedSearchForm, SiteRootAdvancedSearchResultsView, SiteRootAdvancedSearchView +from pyams_content.shared.common import CONTENT_TYPES +from pyams_form.interfaces.form import ISearchFormFactory +from pyams_i18n.interfaces import INegotiator +from pyams_sequence.interfaces import ISequentialIntIds +from pyams_sequence.reference import get_last_version +from pyams_skin.interfaces import IContentSearch +from pyams_skin.layer import IPyAMSLayer +from pyams_template.template import template_config +from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config +from pyams_utils.list import unique +from pyams_utils.registry import get_utility + + +__docformat__ = 'restructuredtext' + +from pyams_content_es import _ + + +class IEsSiteRootAdvancedSearchFields(ISiteRootAdvancedSearchFields): + """Elasticsearch based site root advanced search fields""" + + query = TextLine(title=_("Search query"), + description=_("This query will applies only on title, short name, header " + "and description; if fulltext search option is selected, this " + "will activate search on all paragraphs, including " + "attachments"), + required=False) + + fulltext = Bool(title=_("Fulltext search"), + description=_("Search terms in fulltext mode instead of only on contents " + "titles"), + required=False, + default=False) + + +@template_config(template='templates/advanced-search.pt', layer=IPyAMSLayer) +class EsSiteRootAdvancedSearchForm(SiteRootAdvancedSearchForm): + """Elasticsearch site root advanced search form""" + + fields_interface = IEsSiteRootAdvancedSearchFields + + @property + def fields(self): + fields = super(EsSiteRootAdvancedSearchForm, self).fields + fields['fulltext'].widgetFactory = SingleCheckBoxFieldWidget + return fields + + +@adapter_config(context=(ISiteRoot, IPyAMSLayer, SiteRootAdvancedSearchView), + provides=ISearchFormFactory) +@adapter_config(context=(ISiteRoot, IPyAMSLayer, SiteRootAdvancedSearchResultsView), + provides=ISearchFormFactory) +def site_root_advanced_search_form_factory(context, request, view): + """Elasticsearch site root advanced search form factory""" + return EsSiteRootAdvancedSearchForm(context, request) + + +@adapter_config(context=(ISiteRoot, IPyAMSLayer, EsSiteRootAdvancedSearchForm), + provides=IContentSearch) +class EsSharedToolAdvancedSearchFormSearchAdapter(ContextRequestViewAdapter): + """Elasticsearch site root advanced search form adapter""" + + def get_search_results(self, data): + intids = get_utility(IIntIds) + params = Q('term', parent_ids=intids.register(self.context)) + if data.get('content_type'): + params &= Q('terms', content_type=data['content_type']) + else: + params &= Q('terms', content_type=list(CONTENT_TYPES.keys())) + # check query settings + query = data.get('query') + if query: + sequence = get_utility(ISequentialIntIds) + if query.startswith('+'): + params &= Q('term', reference_id=sequence.get_full_oid(query)) + else: + fulltext = data.get('fulltext', False) + if fulltext: + params &= Q('simple_query_string', query=query) + else: + negotiator = get_utility(INegotiator) + query_params = Q('bool') + for lang in {self.request.registry.settings.get('pyramid.default_locale_name', + 'en'), + self.request.locale_name, + negotiator.server_language} | negotiator.offered_languages: + query_params |= Q('match', **{'title.{}'.format(lang): query}) + query_params |= Q('match', **{'short_name.{}'.format(lang): query}) + query_params |= Q('match', **{'header.{}'.format(lang): query}) + query_params |= Q('match', **{'description.{}'.format(lang): query}) + params &= query_params + if data.get('owner'): + params &= Q('term', owner_id=data['owner']) + if data.get('created_after'): + params &= Q('range', workflow__created_date={'gte': data['created_after']}) + if data.get('created_before'): + params &= Q('range', workflow__created_date={'lte': data['created_before']}) + if data.get('modified_after'): + params &= Q('range', workflow__modified_date={'gte': data['modified_after']}) + if data.get('modified_before'): + params &= Q('range', workflow__modified_date={'lte': data['modified_before']}) + if data.get('tags'): + tags = [intids.register(term) for term in data['tags']] + params &= Q('terms', tags=tags) + if data.get('collections'): + collections = [intids.register(collection) for collection in data['collections']] + params &= Q('terms', collections=collections) + # get ES client and create search object + client = get_client(self.request) + search = Search(using=client.es, index=client.index) \ + .params(request_timeout=30) \ + .query(params) \ + .source(['internal_id']) + sort_values = [{ + 'workflow.modified_date': { + 'unmapped_type': 'date', + 'order': 'desc' + } + }] + if query: + sort_values.insert(0, { + '_score': { + 'order': 'desc' + } + }) + search = search.sort(*sort_values)[:999] + # extract results + return unique(map(get_last_version, + CatalogResultSet([result.internal_id for result in search]))) diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/root/zmi/templates/advanced-search.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content_es/root/zmi/templates/advanced-search.pt Sun Jul 19 02:11:57 2020 +0200 @@ -0,0 +1,246 @@ +
+
+ + +

+ + +
+
+
Form prefix
+ +
+ +
+ +
+
+
Form suffix
+
+
diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/shared/zmi/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content_es/shared/zmi/__init__.py Sun Jul 19 02:11:57 2020 +0200 @@ -0,0 +1,13 @@ +# +# Copyright (c) 2015-2020 Thierry Florac +# 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. +# + + diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/shared/zmi/search.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content_es/shared/zmi/search.py Sun Jul 19 02:11:57 2020 +0200 @@ -0,0 +1,168 @@ +# +# Copyright (c) 2015-2020 Thierry Florac +# 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. +# + +"""PyAMS_content_es.zmi.search module + +""" + +from elasticsearch_dsl import Q, Search +from pyramid_es import get_client +from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget +from zope.dublincore.interfaces import IZopeDublinCore +from zope.intid import IIntIds +from zope.schema import Bool, TextLine + +from pyams_catalog.query import CatalogResultSet +from pyams_content.shared.common import CONTENT_TYPES, IBaseSharedTool +from pyams_content.shared.common.zmi.search import ISharedToolAdvancedSearchFields, \ + SharedToolAdvancedSearchForm, SharedToolAdvancedSearchResultsView, \ + SharedToolAdvancedSearchView +from pyams_form.interfaces.form import ISearchFormFactory +from pyams_i18n.interfaces import INegotiator +from pyams_sequence.interfaces import ISequentialIntIds +from pyams_sequence.reference import get_last_version +from pyams_skin.interfaces import IContentSearch +from pyams_skin.layer import IPyAMSLayer +from pyams_template.template import template_config +from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config +from pyams_utils.list import unique +from pyams_utils.registry import get_utility +from pyams_workflow.interfaces import IWorkflowVersions + + +__docformat__ = 'restructuredtext' + +from pyams_content_es import _ + + +class IEsSharedToolAdvancedSearchFields(ISharedToolAdvancedSearchFields): + """Elasticsearch based shared tool advanced search fields""" + + query = TextLine(title=_("Search query"), + description=_("This query will applies only on title, short name, header " + "and description; if fulltext search option is selected, this " + "will activate search on all paragraphs, including " + "attachments"), + required=False) + + fulltext = Bool(title=_("Fulltext search"), + description=_("Search terms in fulltext mode instead of only on contents " + "titles"), + required=False, + default=False) + + +@template_config(template='templates/advanced-search.pt', layer=IPyAMSLayer) +class EsSharedToolAdvancedSearchForm(SharedToolAdvancedSearchForm): + """Elasticsearch shared tool advanced search form""" + + fields_interface = IEsSharedToolAdvancedSearchFields + + @property + def fields(self): + fields = super(EsSharedToolAdvancedSearchForm, self).fields + fields['fulltext'].widgetFactory = SingleCheckBoxFieldWidget + return fields + + +@adapter_config(context=(IBaseSharedTool, IPyAMSLayer, SharedToolAdvancedSearchView), + provides=ISearchFormFactory) +@adapter_config(context=(IBaseSharedTool, IPyAMSLayer, SharedToolAdvancedSearchResultsView), + provides=ISearchFormFactory) +def shared_tool_advanced_search_form_factory(context, request, view): + """Elasticsearch advanced search form factory""" + return EsSharedToolAdvancedSearchForm(context, request) + + +@adapter_config(context=(IBaseSharedTool, IPyAMSLayer, EsSharedToolAdvancedSearchForm), + provides=IContentSearch) +class EsSharedToolAdvancedSearchFormSearchAdapter(ContextRequestViewAdapter): + """Elasticsearch shared tool advanced search form adapter""" + + def get_search_results(self, data): + intids = get_utility(IIntIds) + params = Q('term', parent_ids=intids.register(self.context)) & \ + Q('terms', content_type=list(CONTENT_TYPES.keys())) + # check query settings + query = data.get('query') + if query: + sequence = get_utility(ISequentialIntIds) + if query.startswith('+'): + params &= Q('term', reference_id=sequence.get_full_oid(query)) + else: + fulltext = data.get('fulltext', False) + if fulltext: + params &= Q('simple_query_string', query=query) + else: + negotiator = get_utility(INegotiator) + query_params = Q('bool') + for lang in {self.request.registry.settings.get('pyramid.default_locale_name', + 'en'), + self.request.locale_name, + negotiator.server_language} | negotiator.offered_languages: + query_params |= Q('match', **{'title.{}'.format(lang): query}) + query_params |= Q('match', **{'short_name.{}'.format(lang): query}) + query_params |= Q('match', **{'header.{}'.format(lang): query}) + query_params |= Q('match', **{'description.{}'.format(lang): query}) + params &= query_params + if data.get('owner'): + params &= Q('term', owner_id=data['owner']) + if data.get('status'): + params &= Q('term', workflow__status=data['status']) + if data.get('data_type'): + params &= Q('term', data_type=data['data_type']) + if data.get('created_after'): + params &= Q('range', workflow__created_date={'gte': data['created_after']}) + if data.get('created_before'): + params &= Q('range', workflow__created_date={'lte': data['created_before']}) + if data.get('modified_after'): + params &= Q('range', workflow__modified_date={'gte': data['modified_after']}) + if data.get('modified_before'): + params &= Q('range', workflow__modified_date={'lte': data['modified_before']}) + if data.get('tags'): + tags = [intids.register(term) for term in data['tags']] + params &= Q('terms', tags=tags) + if data.get('themes'): + themes = [intids.register(term) for term in data['themes']] + params &= Q('terms', themes__terms=themes) + if data.get('collections'): + collections = [intids.register(collection) for collection in data['collections']] + params &= Q('terms', collections=collections) + # get ES client and create search object + client = get_client(self.request) + search = Search(using=client.es, index=client.index) \ + .params(request_timeout=30) \ + .query(params) \ + .source(['internal_id']) + sort_values = [{ + 'workflow.modified_date': { + 'unmapped_type': 'date', + 'order': 'desc' + } + }] + if query: + sort_values.insert(0, { + '_score': { + 'order': 'desc' + } + }) + search = search.sort(*sort_values)[:999] + # extract results + if data.get('status'): + # if status is specified, only extract last version in given status + return unique(map(lambda x: sorted(IWorkflowVersions(x).get_versions(data['status']), + key=lambda y: IZopeDublinCore(y).modified)[0], + CatalogResultSet([result.internal_id for result in search]))) + else: + # otherwise, extract last absolute version + return unique(map(get_last_version, + CatalogResultSet([result.internal_id for result in search]))) diff -r 838fd9ca54fb -r e48a3850c17a src/pyams_content_es/shared/zmi/templates/advanced-search.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content_es/shared/zmi/templates/advanced-search.pt Sun Jul 19 02:11:57 2020 +0200 @@ -0,0 +1,265 @@ +
+
+ + +

+ + +
+
+
Form prefix
+ +
+ +
+ +
+
+
Form suffix
+
+