فهرست منبع

[search] Results highlighting

Romain Rigaux 12 سال پیش
والد
کامیت
59c7909215

+ 10 - 0
apps/search/src/search/forms.py

@@ -48,6 +48,16 @@ class QueryForm(forms.Form):
     return choices and choices[0][0] or None
 
 
+class HighlightingForm(forms.Form):
+  fields = forms.MultipleChoiceField(required=False)
+  is_enabled = forms.BooleanField(label='Enabled', initial=True, required=False)
+
+  def __init__(self, *args, **kwargs):
+    fields = kwargs.pop('fields')
+    super(HighlightingForm, self).__init__(*args, **kwargs)
+    self.fields['fields'].choices = ((name, name) for name in fields)
+
+
 
 class CoreForm(forms.ModelForm):
   class Meta:

+ 25 - 22
apps/search/src/search/models.py

@@ -16,6 +16,7 @@
 # limitations under the License.
 
 from lxml import etree
+import re
 
 try:
   import json
@@ -98,7 +99,7 @@ class Facet(models.Model):
 
 
 class Result(models.Model):
-  _META_TEMPLATE_ATTRS = ['template', 'highlighted_fields', 'css']
+  _META_TEMPLATE_ATTRS = ['template', 'highlighting', 'css']
 
   data = models.TextField()
 
@@ -108,17 +109,34 @@ class Result(models.Model):
     if post_data.get('template'):
       data_dict['template'] = json.loads(post_data['template'])
 
+    if post_data.get('highlighting'):
+      data_dict['highlighting'] = json.loads(post_data['highlighting'])
+
     self.data = json.dumps(data_dict)
 
 
-  def get_template(self):
+  def get_template(self, with_highlighting=False):
+    data_dict = json.loads(self.data)
+
+    template = data_dict.get('template')
+    if with_highlighting:
+      for field in data_dict.get('highlighting', []):
+        template = re.sub('\{\{%s\}\}' % field, '{{{%s}}}' % field, template)
+
+    return template
+
+  def get_query_params(self):
     data_dict = json.loads(self.data)
 
-    return data_dict.get('template')
+    params = ()
 
+    if data_dict.get('highlighting'):
+      params += (
+        ('hl', 'true'),
+        ('hl.fl', ','.join(data_dict.get('highlighting'))),
+      )
 
-  def render_result(self, result):
-    return Template(self.get_template()).render(result=result)
+    return params
 
 
 class Sorting(models.Model):
@@ -146,7 +164,7 @@ class CoreManager(models.Manager):
                    'fields': [],
                    'dates': []
                 }))
-      result = Result.objects.create(data=json.dumps({'template': '{{id}}</br>To customize!<br/>'}))
+      result = Result.objects.create(data=json.dumps({'template': '{{id}}</br>To customize!<br/>', 'highlighting': []}))
       sorting = Sorting.objects.create(data=json.dumps({'fields': []}))
 
       return Core.objects.create(name=name, label=name, facets=facets, result=result, sorting=sorting)
@@ -166,7 +184,7 @@ class Core(models.Model):
   objects = CoreManager()
 
   def get_query(self):
-    return self.facets.get_query_params()
+    return self.facets.get_query_params() + self.result.get_query_params()
 
   def get_absolute_url(self):
     return reverse('search:admin_core', kwargs={'core': self.name})
@@ -180,18 +198,3 @@ class Core(models.Model):
 
 
 class Query(object): pass
-
-
-def temp_fixture_hook():
-  Core.objects.all().delete()
-  if not Core.objects.exists():
-    facets = Facet.objects.create(data=json.dumps({
-                 'ranges': [{"type":"range","field":"price","start":"1","end":"400","gap":"100"}],
-                 'fields': [{"type":"field","field":"id"}],
-                 'dates': [{"type":"date","field":"last_modified","start":"02-13-2013","end":"02-19-2013","gap":"1"}]
-              }))
-    result = Result.objects.create(data=json.dumps({'template': 'To customize!<br/>'}))
-    sorting = Sorting.objects.create(data=json.dumps({'fields': [{"field":"id","label":"My id","asc":False}]}))
-
-    Core.objects.create(name='collection1', label='Tweets', facets=facets, result=result, sorting=sorting)
-    Core.objects.create(name='collection2', label='Zendesk Tickets', facets=facets, result=result, sorting=sorting)

+ 134 - 0
apps/search/src/search/templates/admin_core_highlighting.mako

@@ -0,0 +1,134 @@
+## Licensed to Cloudera, Inc. under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  Cloudera, Inc. licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License.  You may obtain a copy of the License at
+##
+##     http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+
+<%!
+  from desktop.views import commonheader, commonfooter
+  from django.utils.translation import ugettext as _
+%>
+
+<%namespace name="layout" file="layout.mako" />
+<%namespace name="macros" file="macros.mako" />
+
+${ commonheader(_('Search'), "search", user) | n,unicode }
+
+<%layout:skeleton>
+  <%def name="title()">
+    <h1>${_('Search Admin - ')}${hue_core.label}</h1>
+  </%def>
+
+  <%def name="navigation()">
+    ${ layout.sidebar(hue_core.name, 'highlighting') }
+  </%def>
+
+  <%def name="content()">
+    <form method="POST" class="form-horizontal" data-bind="submit: submit">
+      <div class="section">
+        <div class="alert alert-info"><h4>${_('Highlighting')}</h4></div>
+        <div data-bind="visible: highlightedFields().length == 0" style="padding-left: 10px;margin-bottom: 20px">
+          <em>${_('Please select some fields to highlight in the result below.')}</em>
+        </div>
+
+        <div class="clearfix"></div>
+        <div class="miniform">
+          ${_('Is enabled') }
+          </br>
+          <input type='checkbox' data-bind="checked: isEnabled" />
+          </br>
+          ${_('Fields') }
+          </br>
+          <select data-bind="options: fields, selectedOptions: highlightedFields" size="20" multiple="true"></select>
+          <select id="fields" data-bind="options: fields, selectedOptions: highlightedFields" size="20" multiple="true"></select>
+        </div>
+      </div>
+
+      <div class="form-actions" style="margin-top: 80px">
+        <button type="submit" class="btn btn-primary" id="save-btn">${_('Save')}</button>
+      </div>
+    </form>
+  </%def>
+</%layout:skeleton>
+
+<style type="text/css">
+  #fields {
+    list-style-type: none;
+    margin: 0;
+    padding: 0;
+    width: 100%;
+  }
+
+  #fields li {
+    margin-bottom: 10px;
+    padding: 10px;
+    border: 1px solid #E3E3E3;
+    height: 30px;
+  }
+
+  .placeholder {
+    height: 30px;
+    background-color: #F5F5F5;
+    border: 1px solid #E3E3E3;
+  }
+</style>
+
+
+<script src="/static/ext/js/knockout-2.1.0.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/jquery/plugins/jquery-ui-draggable-droppable-sortable-1.8.23.min.js"></script>
+
+<script type="text/javascript">
+  function ViewModel() {
+    var self = this;
+    self.fields = ko.observableArray(${ hue_core.fields | n,unicode });
+
+    self.highlightedFields = ko.observableArray(${ hue_core.result.data | n,unicode }.highlighting);
+    self.isEnabled = ko.observable(true);
+
+    self.submit = function () {
+      $.ajax("${ url('search:admin_core_highlighting', core=hue_core.name) }", {
+        data: {
+          'highlighting': ko.utils.stringifyJson(self.highlightedFields)
+        },
+        contentType: 'application/json',
+        type: 'POST',
+        success: function () {
+          $.jHueNotify.info("${_('Updated')}");
+        },
+        error: function (data) {
+          $.jHueNotify.error("${_('Error: ')}" + data);
+        },
+        complete: function() {
+          $("#save-btn").button('reset');
+        }
+      });
+    };
+  };
+
+  var viewModel = new ViewModel();
+
+  $(document).ready(function () {
+    ko.applyBindings(viewModel);
+
+    ## TODO
+    $("#fields").jHueSelector({
+      selectAllLabel: "${_('Select all')}",
+      searchPlaceholder: "${_('Search')}",
+      noChoicesFound: "${_('No groups found.')}",
+      width:600,
+      height:240
+    });
+  });
+</script>
+
+${ commonfooter(messages) | n,unicode }

+ 2 - 0
apps/search/src/search/templates/admin_core_template.mako

@@ -46,9 +46,11 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
   <%def name="title()">
     <h1>${ _('Template Editor ') } : ${ hue_core.name }</h1>
   </%def>
+
   <%def name="navigation()">
     ${ layout.sidebar(hue_core.name, 'template') }
   </%def>
+
   <%def name="content()">
     <ul class="nav nav-tabs">
       <li class="active"><a href="#visual" data-toggle="tab">${_('Visual editor')}</a></li>

+ 8 - 2
apps/search/src/search/templates/index.mako

@@ -150,9 +150,15 @@ ${ commonheader(_('Search'), "search", user, "40px") | n,unicode }
 
       <div id="result-container"></div>
       <script>
-        $.each(${ json.dumps([result for result in response['response']['docs']]) | n,unicode }, function (index, item) {
+      <%
+        docs = response['response']['docs']
+        for doc in response['response']['docs']:
+          if doc['id'] in response['highlighting']:
+            doc.update(response['highlighting'][doc['id']])
+        %>
+        $.each(${ json.dumps([result for result in docs]) | n,unicode }, function (index, item) {
           $("<div>").addClass("result-row").html(
-            Mustache.render(${ hue_core.result.data | n,unicode }.template, item)
+            Mustache.render('${ hue_core.result.get_template(with_highlighting=True) | n,unicode }', item)
           ).appendTo($("#result-container"));
         });
       </script>

+ 4 - 1
apps/search/src/search/templates/layout.mako

@@ -87,8 +87,11 @@
       <li class="${ utils.is_selected(section, 'sorting') }">
         <a href="${ url('search:admin_core_sorting', core=core) }">${_('3. Sorting')}</a>
       </li>
+      <li class="${ utils.is_selected(section, 'highlighting') }">
+        <a href="${ url('search:admin_core_highlighting', core=core) }">${_('4. Highlighting')}</a>
+      </li>
       <li class="${ utils.is_selected(section, 'autocomplete') }">
-        <a href="${ url('search:admin_core_sorting', core=core) }">${_('4. Auto-complete')}</a>
+        <a href="${ url('search:admin_core_sorting', core=core) }">${_('5. Auto-complete')}</a>
       </li>
       <li class="nav-header">${_('Search')}</li>
       <li>

+ 4 - 1
apps/search/src/search/urls.py

@@ -21,13 +21,16 @@ urlpatterns = patterns('search.views',
   url(r'^$', 'index', name='index'),
   url(r'^query$', 'index', name='query'),
   url(r'^admin$', 'admin', name='admin'),
+
   url(r'^admin/cores$', 'admin', name='admin_cores'),
+
   url(r'^admin/core/(?P<core>\w+)$', 'admin_core_template', name='admin_core'),
   url(r'^admin/core/(?P<core>\w+)/properties$', 'admin_core_properties', name='admin_core_properties'),
-  url(r'^admin/core/(?P<core>\w+)/solr_properties$', 'admin_core_solr_properties', name='admin_core_solr_properties'),
   url(r'^admin/core/(?P<core>\w+)/schema$', 'admin_core_schema', name='admin_core_schema'),
   url(r'^admin/core/(?P<core>\w+)/template$', 'admin_core_template', name='admin_core_template'),
   url(r'^admin/core/(?P<core>\w+)/facets$', 'admin_core_facets', name='admin_core_facets'),
+  url(r'^admin/core/(?P<core>\w+)/highlighting$', 'admin_core_highlighting', name='admin_core_highlighting'),
   url(r'^admin/core/(?P<core>\w+)/sorting$', 'admin_core_sorting', name='admin_core_sorting'),
+  url(r'^admin/core/(?P<core>\w+)/solr_properties$', 'admin_core_solr_properties', name='admin_core_solr_properties'),
 
 )

+ 22 - 5
apps/search/src/search/views.py

@@ -32,17 +32,14 @@ from desktop.lib.django_util import render
 from search.api import SolrApi
 from search.conf import SOLR_URL
 from search.decorators import allow_admin_only
-from search.forms import QueryForm, CoreForm
-from search.models import Core, temp_fixture_hook
+from search.forms import QueryForm, CoreForm, HighlightingForm
+from search.models import Core
 
 
 LOG = logging.getLogger(__name__)
 
 
 def index(request):
-  #Core.objects.all().delete()
-  ##temp_fixture_hook()
-
   cores = SolrApi(SOLR_URL.get()).cores()
   hue_cores = Core.objects.all()
 
@@ -209,3 +206,23 @@ def admin_core_sorting(request, core):
     'hue_core': hue_core,
     'hue_cores': hue_cores,
   })
+
+
+@allow_admin_only
+def admin_core_highlighting(request, core):
+  solr_core = SolrApi(SOLR_URL.get()).core(core)
+  hue_core = Core.objects.get(name=core)
+  hue_cores = Core.objects.all()
+
+  if request.method == 'POST':
+    # TODO on/off + form actually
+    hue_core.result.update_from_post(request.POST)
+    hue_core.result.save()
+    return HttpResponse(json.dumps({}), mimetype="application/json")
+
+  return render('admin_core_highlighting.mako', request, {
+    'solr_core': solr_core,
+    'hue_core': hue_core,
+    'hue_cores': hue_cores,
+  })
+