Newer
Older
# -*- coding: utf-8 -*-
# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr -- mailto:contact@logilab.fr
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

Arthur Lutz
committed
"""elasticsearch search views"""
from elasticsearch import Elasticsearch
from elasticsearch.exceptions import NotFoundError
from elasticsearch_dsl import Search

Arthur Lutz
committed
from elasticsearch_dsl.connections import connections
from elasticsearch_dsl import FacetedSearch, TermsFacet, DateHistogramFacet

Arthur Lutz
committed
from bs4 import BeautifulSoup
from logilab.mtconverter import xml_escape
from cubicweb.view import StartupView
from cubes.elasticsearch.es import indexable_types

Arthur Lutz
committed
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def get_connection(config):
try:
connections.get_connection()
except KeyError:
locations = config['elasticsearch-locations']
index_name = config['index-name']
# TODO sanitize locations
connections.create_connection(hosts=locations.split(','),
index=index_name,
timeout=20)
class CWFacetedSearch(FacetedSearch):
#doc_types = ['NewsContent', 'CommemorationItem', 'Circular',]
# fields that should be searched
fields = ["title^3", "name^3", "content^2", 'content', '_all',]
facets = {
# use bucket aggregations to define facets
'cw_etype': TermsFacet(field='cw_etype'),
'creation_date': DateHistogramFacet(field='creation_date', interval='month'),
'commemoration_year': DateHistogramFacet(field='commemoration_year', interval='year'),
'year': DateHistogramFacet(field='year', interval='year'),
}
def __init__(self, query=None, filters={}, doc_types=None):
if doc_types:
self.doc_types = doc_types
super(CWFacetedSearch, self).__init__(query, filters)
def highlight(self, search):
"""
Add custom highlighting
"""
return search.highlight(*self.fields) \
.highlight_options(pre_tags="",
post_tags="",
fragment_size=150)

Arthur Lutz
committed
class ElasticSearchView(StartupView):
__regid__ = "esearch"
def call(self, **kwargs):

Arthur Lutz
committed
# TODO if no ES configuration, redirect or display warning

Arthur Lutz
committed
search_comp = self._cw.vreg['components'].select_or_none('search-comp',
self._cw)
if search_comp:
search_comp.render(w=self.w)
self.w(u'<h1>%s</h1>' % self._cw._('Recherche'))
query_string = xml_escape(self._cw.form.get('search', ''))
self.w(u'<h2>Résultats pour : <em>%s</em></h2>' % query_string)

Arthur Lutz
committed
get_connection(self._cw.vreg.config)
search = CWFacetedSearch(query_string,
doc_types = indexable_types(self._cw.vreg.schema))

Arthur Lutz
committed
try:
response = search.execute()
except NotFoundError:
self.w(u'index not found in elasticsearch')
return
self.w(u'Resultats: %s' % response.hits.total)

Arthur Lutz
committed
self.display_facets(response)
self.display_results(response)
def display_results(self, response):
self.w(u'<div id="main-center" class="col-xs-10" role="main">')

Arthur Lutz
committed
self.w(u'<ul>')
for result in response:
self.w(u'<li>')
infos = result.to_dict()
infos['_score'] = result.meta.score
infos['keys'] = result.to_dict().keys()
infos.setdefault('title', infos.get('name', infos.get('reference', u'n/a')))
try:
self.w(u'<a href="%(cwuri)s">%(title)s</a> (%(_score).2f)<br/>' % (infos))
if self._cw.form.get('debug-es'):
self.w(u' [%(keys)s] <br/>' % infos)
except KeyError:
self.w(u'Missing key in : %s' % infos.keys())
try:
for fragment in result.meta.highlight.content:
self.w(u'... %s' % BeautifulSoup(fragment, 'lxml').get_text())
self.w(u' ... <br/>')
except AttributeError:
pass
self.w(u'</li>')
self.w(u'</ul>')

Arthur Lutz
committed
self.w(u'</div>')
def display_facets(self, response):
self.w(u'''<aside id="aside-main-left" class="col-xs-2 cwjs-aside">
<div class="panel panel-default contextFreeBox facet_filterbox">
<div class="panel-heading">
<div class="panel-title">facettes</div>
</div>
''')
for attribute in ('cw_etype', 'creation_date'):
self.w(u'<div class="facetBody vocabularyFacet">')
self.w(u'<div class="facetTitle">{}</div>'.format(attribute))
for (tag, count, selected) in response.facets[attribute]:
# facetValueSelected / facetValueDisabled in class
facet_item = u'<div class="facetValue facetCheckBox"><span>{} {} {}</span></div>'
self.w(facet_item.format(tag, ' (SELECTED):' if selected else ':', count))
self.w(u'</div>')
self.w(u'</div></aside>')