Przeglądaj źródła

[search] Generic collections and customizable UI skeleton

Configure one field facet of each core
Romain Rigaux 12 lat temu
rodzic
commit
1d2b022264

+ 1 - 1
apps/oozie/src/oozie/templates/editor/edit_coordinator.mako

@@ -137,7 +137,7 @@ ${ layout.menubar(section='coordinators') }
               </div>
               <div class="row-fluid">
                 <div class="span6">
-                ${ utils.render_field_no_popover(coordinator_form['start']) }
+                ${ utils.render_field_popover(coordinator_form['start']) }
                 </div>
                 <div class="span6">
                 ${ utils.render_field_no_popover(coordinator_form['end']) }

+ 81 - 0
apps/search/src/search/api.py

@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# 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.
+
+try:
+  import json
+except ImportError:
+  import simplejson as json
+
+import logging
+
+from desktop.lib.exceptions_renderable import PopupException
+from desktop.lib.rest.http_client import HttpClient, RestException
+from desktop.lib.rest.resource import Resource
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SolrApi(object):
+  """
+  http://wiki.apache.org/solr/CoreAdmin#CoreAdminHandler
+  """
+  def __init__(self, solr_url):
+    self._url = solr_url
+    self._client = HttpClient(self._url, logger=LOG)
+    self._root = Resource(self._client)
+
+  def query(self, solr_query, hue_core):
+    try:
+      params = (('q', solr_query['q']),
+                ('wt', 'json'),
+                ('rows', solr_query['rows']),
+                ('start', solr_query['start']),
+                ('facet', solr_query['facets'] == 1 and 'true' or 'false'),
+             )
+
+      params += hue_core.get_query()
+      #('sort', solr_query['sort']),
+
+      fqs = solr_query['fq'].split('|')
+      for fq in fqs:
+        if fq:
+          params += (('fq', fq),)
+
+      response = self._root.get('%(core)s/browse' % solr_query, params)
+      return json.loads(response)
+    except RestException, e:
+      raise PopupException('Error while accessing Solr: %s' % e)
+
+  def cores(self):
+    try:
+      return self._root.get('admin/cores', params={'wt': 'json'})
+    except RestException, e:
+      raise PopupException('Error while accessing Solr: %s' % e)
+
+  def core(self, core):
+    try:
+      return self._root.get('admin/cores', params={'wt': 'json', 'core': core})
+    except RestException, e:
+      raise PopupException('Error while accessing Solr: %s' % e)
+
+  def schema(self, core):
+    try:
+      return self._root.get('%(core)s/admin/file' % {'core': core}, params={'wt': 'json', 'file': 'schema.xml'})
+    except RestException, e:
+      raise PopupException('Error while accessing Solr: %s' % e)
+

+ 27 - 0
apps/search/src/search/conf.py

@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# 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 django.utils.translation import ugettext_lazy as _
+
+from desktop.lib.conf import Config
+
+
+SOLR_URL = Config(
+  key="solr_url",
+  help=_("URL of the Solr Server."),
+  private=False,
+  default="http://localhost:1978/solr/")

+ 36 - 0
apps/search/src/search/decorators.py

@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# 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.
+
+import logging
+
+from django.utils.functional import wraps
+from django.utils.translation import ugettext as _
+
+from desktop.lib.exceptions_renderable import PopupException
+
+LOG = logging.getLogger(__name__)
+
+
+def allow_admin_only(view_func):
+  def decorate(request, *args, **kwargs):
+
+    if not request.user.is_superuser:
+      message = _("Permission denied. You are not an Administrator")
+      raise PopupException(message)
+
+    return view_func(request, *args, **kwargs)
+  return wraps(view_func)(decorate)

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

@@ -17,9 +17,12 @@
 
 
 from django import forms
+from search.models import Core
 
 
 class QueryForm(forms.Form):
+  cores = forms.ChoiceField()
+
   query = forms.CharField(label='', max_length=256, required=False, initial='',
                           widget=forms.TextInput(attrs={'class': 'input-xxlarge search-query', 'placeholder': 'Search'}))
   fq = forms.CharField(label='', max_length=256, required=False, initial='', widget=forms.HiddenInput(), help_text='Solr Filter query')
@@ -27,3 +30,19 @@ class QueryForm(forms.Form):
   rows = forms.CharField(label='', required=False, initial='', widget=forms.HiddenInput(), help_text='Solr records per page')
   start = forms.CharField(label='', required=False, initial='', widget=forms.HiddenInput(), help_text='Solr start record')
   facets = forms.CharField(label='', required=False, initial='', widget=forms.HiddenInput(), help_text='Show hide facet search')
+
+  def __init__(self, *args, **kwargs):
+    super(QueryForm, self).__init__(*args, **kwargs)
+    choices = [(core.name, core.label) for core in Core.objects.filter(enabled=True)]
+    initial_choice = self._initial_core(choices)
+    self.fields['cores'] = forms.ChoiceField(choices=choices, initial=initial_choice, required=False, label='')
+
+  def clean_cores(self):
+    if self.cleaned_data.get('cores'):
+      return self.cleaned_data['cores']
+    else:
+      return self._initial_core(self.fields['cores'].choices)
+
+
+  def _initial_core(self, choices):
+    return choices and choices[0][0] or None

+ 151 - 0
apps/search/src/search/models.py

@@ -0,0 +1,151 @@
+#!/usr/bin/env python
+# 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 lxml import etree
+
+try:
+  import json
+except ImportError:
+  import simplejson as json
+
+import logging
+
+from django.db import models
+from django.utils.translation import ugettext as _, ugettext_lazy as _t
+from django.core.urlresolvers import reverse
+from mako.template import Template
+
+from search.api import SolrApi
+from search.conf import SOLR_URL
+
+LOG = logging.getLogger(__name__)
+
+
+class RangeFacet(object): pass
+class DateFacet(object): pass
+
+
+class Facet(models.Model):
+  _ATTRIBUTES = ['properties', 'fields', 'range', 'date'] # Metadata of the data, only 'fields' used currently
+  enabled = models.BooleanField(default=True)
+  data = models.TextField()
+
+  def dumps(self):
+    pass
+
+  def loads(self, json):
+    pass
+
+  def update_from_post(self, post_data):
+    data_dict = json.loads(self.data)
+
+    if 'fields' in post_data and post_data['fields']:
+      data_dict['fields'] = json.loads(post_data['fields'])
+
+    self.data = json.dumps(data_dict)
+
+  def get_query_params(self):
+    data_dict = json.loads(self.data)
+
+    params = (
+        ('facet', 'true'),
+        ('facet.limit', 10),
+        ('facet.mincount', 1),
+        ('facet.sort', 'count'),
+    )
+
+    if 'fields' in data_dict and data_dict['fields']:
+      field_facets = tuple([('facet.field', field_facet) for field_facet in data_dict['fields']])
+      params += field_facets
+
+    return params
+
+# e.g.
+#                ('facet.range', 'retweet_count'),
+#                ('f.retweet_count.facet.range.start', '0'),
+#                ('f.retweet_count.facet.range.end', '100'),
+#                ('f.retweet_count.facet.range.gap', '10'),
+#
+#                ('facet.date', 'created_at'),
+#                ('facet.date.start', 'NOW/DAY-305DAYS'),
+#                ('facet.date.end', 'NOW/DAY+1DAY'),
+#                ('facet.date.gap', '+1DAY'),
+
+
+class Result(models.Model):
+  _META_TEMPLATE_ATTRS = ['template', 'highlighted_fields', 'css']
+
+  def __init__(self, *args, **kwargs):
+    super(Result, self).__init__(*args, **kwargs)
+    self._data_dict = []
+
+  data = models.TextField()
+
+  def gen_template(self):
+    return """
+      <tr>
+        <td style="word-wrap: break-word;">
+          <div class="content">
+            <div class="text">
+              ${ result.get('id', '')  | n,unicode }
+            </div>
+          </div>
+        </td>
+      </tr>"""
+
+  def gen_result(self, result):
+    return Template(self.gen_template()).render(result=result)
+
+
+class Sorting(models.Model): pass
+
+
+class Core(models.Model):
+  enabled = models.BooleanField(default=True)
+  name = models.CharField(max_length=40, unique=True, help_text=_t('Name of the Solr collection'))
+  label = models.CharField(max_length=100)
+  properties = models.TextField(default='[]', verbose_name=_t('Core properties'), help_text=_t('Properties (e.g. facets off, results by pages number)'))
+  facets = models.ForeignKey(Facet)
+  result = models.ForeignKey(Result)
+  sorting = models.ForeignKey(Sorting)
+
+  def get_query(self):
+    return self.facets.get_query_params()
+
+  def get_absolute_url(self):
+    return reverse('search:admin_core', kwargs={'core': self.name})
+
+  @property
+  def fields(self):
+    solr_schema = SolrApi(SOLR_URL.get()).schema(self.name)
+    schema = etree.fromstring(solr_schema)
+
+    return [field.get('name') for fields in schema.iter('fields') for field in fields.iter('field')]
+
+
+class Query(object): pass
+
+
+def temp_fixture_hook():
+  #Core.objects.all().delete()
+  if not Core.objects.exists():
+    facets = Facet.objects.create(data=json.dumps({'fields': ['id']}))
+    result = Result.objects.create()
+    sorting = Sorting.objects.create()
+
+    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)

+ 41 - 0
apps/search/src/search/templates/admin.mako

@@ -0,0 +1,41 @@
+## 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="macros" file="macros.mako" />
+<%namespace name="navigation" file="navigation_bar_admin.mako" />
+
+${ commonheader(_('Search'), "search", user) | n,unicode }
+
+
+<div class="container-fluid">
+  ${ navigation.menubar('cores') }
+
+  % for core in hue_cores:
+    <p><a href="${ core.get_absolute_url() }">${ core.name }</a></p>
+  % endfor
+
+
+  ${ cores }
+  ${ hue_cores }
+
+</div>
+
+${ commonfooter(messages) | n,unicode }

+ 49 - 0
apps/search/src/search/templates/admin_core.mako

@@ -0,0 +1,49 @@
+## 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="macros" file="macros.mako" />
+<%namespace name="navigation" file="navigation_bar_admin.mako" />
+
+${ commonheader(_('Search'), "search", user) | n,unicode }
+
+
+<div class="container-fluid">
+  ${ navigation.menubar('cores') }
+  ${ navigation.sub_menubar(hue_core.name, 'properties') }
+
+  Solr
+  <p>
+  ${ solr_core }
+  </p>
+
+  Hue
+  <p>
+  ${ hue_core }
+  </p>
+
+  Schema
+  <p>
+  ${ solr_schema.decode('utf-8') }
+  </p>
+
+</div>
+
+${ commonfooter(messages) | n,unicode }

+ 100 - 0
apps/search/src/search/templates/admin_core_facets.mako

@@ -0,0 +1,100 @@
+## 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="macros" file="macros.mako" />
+<%namespace name="navigation" file="navigation_bar_admin.mako" />
+
+${ commonheader(_('Search'), "search", user) | n,unicode }
+
+
+<div class="container-fluid">
+  ${ navigation.menubar('cores') }
+  ${ navigation.sub_menubar(hue_core.name, 'facets') }
+
+  Solr
+  <p>
+  ${ solr_core }
+  </p>
+
+  Hue
+  <p>
+  ${ hue_core }
+  </p>
+
+  <!--
+  <div class="btn-group" style="display: inline">
+    <a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
+      <i class="icon-plus-sign"></i> New
+      <span class="caret"></span>
+    </a>
+    <ul class="dropdown-menu" style="top: auto">
+      <li><a href="#" class="create-file-link" title="${ _('Field facet') }"><i class="icon-bookmark"></i> ${ _('Field') }</a></li>
+      <li><a href="#" class="create-directory-link" title="${ _('Range') }"><i class="icon-resize-full"></i> ${ _('Range') }</a></li>
+      <li><a href="#" class="create-directory-link" title="Directory"><i class="icon-calendar"></i> ${ _('Date') }</a></li>
+    </ul>
+  </div>
+  -->
+
+  <form method="POST" id="facets" data-bind="submit: submit">
+    <ul data-bind="foreach: field_facets">
+      <li>
+        <span data-bind="text: $data"></span>
+        <button data-bind="click: $parent.removeFieldFacets">${ _('Remove') }</button>
+      </li>
+    </ul>
+
+    <button type="submit">${ _('Save') }</button>
+  </form>
+
+
+  <select data-bind="options: fields, selectedOptions: field_facets" size="5" multiple="true"></select>
+
+  ${ hue_core.fields }
+
+</div>
+
+<script src="/static/ext/js/knockout-2.1.0.js" type="text/javascript" charset="utf-8"></script>
+
+<script type="text/javascript">
+  $(document).ready(function(){
+     function ViewModel() {
+       var self = this;
+       self.fields = ko.observableArray(${ hue_core.fields | n,unicode });
+       self.field_facets = ko.observableArray(${ hue_core.facets.data | n,unicode }.fields);
+
+       self.removeFieldFacets = function(facet) {
+         self.field_facets.remove(facet);
+       };
+
+      self.submit = function() {
+	    $.ajax("${ url('search:admin_core_facets', core=hue_core.name) }", {
+		    data : {'fields': ko.utils.stringifyJson(self.field_facets)},
+		    contentType : 'application/json',
+		    type : 'POST'
+	    }); // notif + refresh?
+      };
+    };
+
+    ko.applyBindings(new ViewModel());
+  });
+</script>
+
+${ commonfooter(messages) | n,unicode }

+ 44 - 0
apps/search/src/search/templates/admin_core_result.mako

@@ -0,0 +1,44 @@
+## 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="macros" file="macros.mako" />
+<%namespace name="navigation" file="navigation_bar_admin.mako" />
+
+${ commonheader(_('Search'), "search", user) | n,unicode }
+
+
+<div class="container-fluid">
+  ${ navigation.menubar('cores') }
+  ${ navigation.sub_menubar(hue_core.name, 'result') }
+
+  Solr
+  <p>
+  ${ solr_core }
+  </p>
+
+  Hue
+  <p>
+  ${ hue_core }
+  </p>
+
+</div>
+
+${ commonfooter(messages) | n,unicode }

+ 44 - 0
apps/search/src/search/templates/admin_core_sorting.mako

@@ -0,0 +1,44 @@
+## 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="macros" file="macros.mako" />
+<%namespace name="navigation" file="navigation_bar_admin.mako" />
+
+${ commonheader(_('Search'), "search", user) | n,unicode }
+
+
+<div class="container-fluid">
+  ${ navigation.menubar('cores') }
+  ${ navigation.sub_menubar(hue_core.name, 'sorting') }
+
+  Solr
+  <p>
+  ${ solr_core }
+  </p>
+
+  Hue
+  <p>
+  ${ hue_core }
+  </p>
+
+</div>
+
+${ commonfooter(messages) | n,unicode }

+ 36 - 0
apps/search/src/search/templates/admin_settings.mako

@@ -0,0 +1,36 @@
+## 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="macros" file="macros.mako" />
+<%namespace name="navigation" file="navigation_bar_admin.mako" />
+
+${ commonheader(_('Search'), "search", user) | n,unicode }
+
+
+<div class="container-fluid">
+  ${ navigation.menubar('settings') }
+
+  ${ cores }
+  ${ hue_cores }
+
+</div>
+
+${ commonfooter(messages) | n,unicode }

+ 127 - 115
apps/search/src/search/templates/index.mako

@@ -17,8 +17,6 @@
 <%!
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
-from itertools import izip
-import math
 %>
 
 <%namespace name="macros" file="macros.mako" />
@@ -27,130 +25,144 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 
 
 <div class="container-fluid">
-    <div class="row-fluid">
-        % if solr_query['facets'] == 1:
-        <div class="span3">
-            <div class="well" style="padding: 8px 0;">
-            <ul class="nav nav-list">
-                % if solr_query['fq']:
-                    <li class="nav-header">${_('Current filter')}</li>
-                    %for fq in solr_query['fq'].split('|'):
-                      %if fq:
-                        <%
-                          removeList = solr_query['fq'].split('|')
-                          removeList.remove(fq)
-                        %>
-                        <li><a href="?query=${ solr_query['q'] }&fq=${'|'.join(removeList)}&sort=${solr_query["sort"]}">${fq} <i class="icon-trash"></i></a></li>
-                      %endif
-                    %endfor
-                    <li style="margin-bottom: 20px"></li>
-                % endif
-                % if response and response['facet_counts']:
-                    % if response['facet_counts']['facet_fields']:
-                        <h4>${_('Fields')}</h4>
-                        % for cat in response['facet_counts']['facet_fields']:
-                            % if response['facet_counts']['facet_fields'][cat]:
-                            <li class="nav-header">${cat}</li>
-                            % for subcat, count in macros.pairwise(response['facet_counts']['facet_fields'][cat]):
-                              %if count > 0 and subcat != "":
-                                <li><a href="?query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ cat }:${ subcat }&sort=${solr_query["sort"]}">${subcat} (${ count })</a></li>
-                              %endif
-                            % endfor
-                            % endif
-                        % endfor
-                    %endif
-
-                    % if response['facet_counts']['facet_ranges']:
-                        <h4>${_('Ranges')}</h4>
-                        % for cat in response['facet_counts']['facet_ranges']:
-                            % if response['facet_counts']['facet_ranges'][cat]:
-                            <li class="nav-header">${cat}</li>
-                            % for range, count in macros.pairwise(response['facet_counts']['facet_ranges'][cat]['counts']):
-                              %if count > 0:
-                                <li><a href="?query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ cat }:${ range }&sort=${solr_query["sort"]}">${ range } (${ count })</a></li>
-                              %endif
-                            % endfor
-                            % endif
-                        % endfor
-                    %endif
-
-                    % if response['facet_counts']['facet_dates']:
-                        <h4>${_('Dates')}</h4>
-                        % for cat in response['facet_counts']['facet_dates']:
-                            % if response['facet_counts']['facet_dates'][cat]:
-                            <li class="nav-header">${cat}</li>
-                            % for date, count in response['facet_counts']['facet_dates'][cat].iteritems():
-                              % if date not in ('start', 'end', 'gap') and count > 0:
-                                <li><a href="?query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ cat }:${ date }&sort=${solr_query["sort"]}">${ date } (${ count })</a></li>
-                              % endif
-                            % endfor
-                            % endif
-                        % endfor
-                    %endif
-                %endif
-            </ul>
-            </div>
+
+  <div class="row-fluid">
+    <div class="span12">
+      <form class="form-search well">
+        <i class="twitter-logo"></i>
+        ${ search_form | n,unicode }
+        <button class="btn" type="submit">${_('Search')}</button>
+        % if response:
+        <div class="btn-group pull-right">
+          <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
+            ${_('Sort by')}
+            <span class="caret"></span>
+          </a>
+          <ul class="dropdown-menu">
+            <li><a href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=created_at+desc&rows=${solr_query["rows"]}&start=${solr_query["start"]}">${_('Date')}</a></li>
+            <li><a href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=retweet_count+desc&rows=${solr_query["rows"]}&start=${solr_query["start"]}">${_('Retweets count')}</a></li>
+            <li class="divider"></li>
+            <li><a href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&rows=${solr_query["rows"]}&start=${solr_query["start"]}">${_('Reset sorting')}</a></li>
+          </ul>
+          % endif
+          % if user.is_superuser:
+            <a class="btn" href="${ url('search:admin') }">${ _('Admin') }</a>
+          % endif
         </div>
-        <div class="span9">
-        % else:
-        <div class="span12">
+      </form>
+    </div>
+  </div>
+
+  % if response and solr_query['facets'] == 1:
+  <div class="row-fluid">
+    <div class="span3">
+      <div class="well" style="padding: 8px 0;">
+      <ul class="nav nav-list">
+        % if solr_query['fq']:
+          <li class="nav-header">${_('Current filter')}</li>
+          % for fq in solr_query['fq'].split('|'):
+            % if fq:
+              <%
+                removeList = solr_query['fq'].split('|')
+                removeList.remove(fq)
+              %>
+              <li><a href="?query=${ solr_query['q'] }&fq=${'|'.join(removeList)}&sort=${solr_query["sort"]}">${fq} <i class="icon-trash"></i></a></li>
+            % endif
+          % endfor
+          <li style="margin-bottom: 20px"></li>
         % endif
-            <form class="form-search well">
-                <i class="twitter-logo"></i>
-                ${ search_form | n,unicode }
-                <button class="btn" type="submit">${_('Search')}</button>
-                <div class="btn-group pull-right">
-                  <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
-                    ${_('Sort by')}
-                    <span class="caret"></span>
-                  </a>
-                  <ul class="dropdown-menu">
-                    <li><a href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=created_at+desc&rows=${solr_query["rows"]}&start=${solr_query["start"]}">${_('Date')}</a></li>
-                    <li><a href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=retweet_count+desc&rows=${solr_query["rows"]}&start=${solr_query["start"]}">${_('Retweets count')}</a></li>
-                    <li class="divider"></li>
-                    <li><a href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&rows=${solr_query["rows"]}&start=${solr_query["start"]}">${_('Reset sorting')}</a></li>
-                  </ul>
-                </div>
-            </form>
-
-            % if response:
-                <table class="table table-striped table-hover" style="table-layout: fixed;">
-                <tbody>
-                % for result in response['response']['docs']:
-                    ${ macros.tweet_result(result) }
+
+        % if response and response['facet_counts']:
+          % if response['facet_counts']['facet_fields']:
+            <h4>${_('Fields')}</h4>
+            % for cat in response['facet_counts']['facet_fields']:
+                % if response['facet_counts']['facet_fields'][cat]:
+                <li class="nav-header">${cat}</li>
+                % for subcat, count in macros.pairwise(response['facet_counts']['facet_fields'][cat]):
+                  %if count > 0 and subcat != "":
+                    <li><a href="?query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ cat }:${ subcat }&sort=${solr_query["sort"]}">${subcat} (${ count })</a></li>
+                  %endif
                 % endfor
-                </tbody>
-                </table>
-                  <div class="pagination">
-                    <ul class="pull-right">
-                      <%
-                        beginning = 0
-                        previous = int(solr_query["start"]) - int(solr_query["rows"])
-                        next = int(solr_query["start"]) + int(solr_query["rows"])
-                      %>
-                      % if int(solr_query["start"]) > 0:
-                        <li><a title="${_('Beginning of List')}" href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=${solr_query["sort"]}&rows=${solr_query["rows"]}&start=${beginning}">&larr; ${_('Beginning of List')}</a></li>
-                        <li><a title="Previous Page" href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=${solr_query["sort"]}&rows=${solr_query["rows"]}&start=${previous}">${_('Previous Page')}</a></li>
-                      % endif
-                      <li><a title="Next page" href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=${solr_query["sort"]}&rows=${solr_query["rows"]}&start=${next}">${_('Next Page')}</a></li>
-                    </ul>
-                    <p>${_('Show')}
-                      <select id="recordsPerPage" class="input-mini"><option value="15">15</option><option value="30">30</option><option value="45">45</option><option value="60">60</option><option value="100">100</option></select>
-                      ${_('tweets per page.')}
-                      ${_('Showing %s to %s of %s tweets') % (int(solr_query["start"])+1, int(solr_query["start"])+int(solr_query["rows"]), response['response']['numFound'])}
-                    </p>
-                  </div>
-            % endif
-        </div>
+                % endif
+            % endfor
+          %endif
+
+          % if response['facet_counts']['facet_ranges']:
+            <h4>${_('Ranges')}</h4>
+            % for cat in response['facet_counts']['facet_ranges']:
+                % if response['facet_counts']['facet_ranges'][cat]:
+                <li class="nav-header">${cat}</li>
+                % for range, count in macros.pairwise(response['facet_counts']['facet_ranges'][cat]['counts']):
+                 % if count > 0:
+                   <li><a href="?query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ cat }:${ range }&sort=${solr_query["sort"]}">${ range } (${ count })</a></li>
+                  %endif
+                % endfor
+              % endif
+            % endfor
+          % endif
+
+          % if response['facet_counts']['facet_dates']:
+            <h4>${_('Dates')}</h4>
+            % for cat in response['facet_counts']['facet_dates']:
+                % if response['facet_counts']['facet_dates'][cat]:
+                <li class="nav-header">${cat}</li>
+                % for date, count in response['facet_counts']['facet_dates'][cat].iteritems():
+                  % if date not in ('start', 'end', 'gap') and count > 0:
+                    <li><a href="?query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ cat }:${ date }&sort=${solr_query["sort"]}">${ date } (${ count })</a></li>
+                  % endif
+                % endfor
+                % endif
+            % endfor
+          %endif
+         %endif
+      </ul>
+      </div>
     </div>
+    % endif
+
+    <div class="span9">
+    % if response:
+      <table class="table table-striped table-hover" style="table-layout: fixed;">
+        <tbody>
+          % for result in response['response']['docs']:
+            ${ hue_core.result.gen_result(result) | n,unicode }
+          % endfor
+        </tbody>
+      </table>
+
+      <div class="pagination">
+        <ul class="pull-right">
+          <%
+            beginning = 0
+            previous = int(solr_query["start"]) - int(solr_query["rows"])
+            next = int(solr_query["start"]) + int(solr_query["rows"])
+          %>
+          % if int(solr_query["start"]) > 0:
+            <li><a title="${_('Beginning of List')}" href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=${solr_query["sort"]}&rows=${solr_query["rows"]}&start=${beginning}">&larr; ${_('Beginning of List')}</a></li>
+            <li><a title="Previous Page" href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=${solr_query["sort"]}&rows=${solr_query["rows"]}&start=${previous}">${_('Previous Page')}</a></li>
+          % endif
+          <li><a title="Next page" href="?query=${solr_query["q"]}&fq=${solr_query["fq"]}&sort=${solr_query["sort"]}&rows=${solr_query["rows"]}&start=${next}">${_('Next Page')}</a></li>
+        </ul>
+        <p>
+          ${_('Showing %s to %s of %s tweets') % (int(solr_query["start"])+1, int(solr_query["start"])+int(solr_query["rows"]), response['response']['numFound'])}
+          ##${_('Show')}
+          ##<select id="recordsPerPage" class="input-mini"><option value="15">15</option><option value="30">30</option><option value="45">45</option><option value="60">60</option><option value="100">100</option></select>
+          ##${_('tweets per page.')}
+        </p>
+      </div>
+    % endif
+    </div>
+  </div>
 </div>
+
 <div class="hide">
-  ${rr}
+  ${ rr | n,unicode }
 </div>
 
 <link rel="stylesheet" href="/search/static/css/search.css">
 
 <script src="/static/ext/js/moment.min.js" type="text/javascript" charset="utf-8"></script>
+
 <script>
   $(document).ready(function(){
     $("a[data-dt]").each(function(){

+ 41 - 42
apps/search/src/search/templates/macros.mako

@@ -175,49 +175,48 @@ def escape(text)  :
 
 <%def name="tweet_result(result)">
 <tr>
-    <td style="word-wrap: break-word;">
-
-      <div class="content">
-        <div class="stream-item-header">
-          <small class="time">
-            <a href="https://twitter.com/${ result.get('user_screen_name', '') }/status/${ result.get('id', '') }" target="_blank" data-dt="${ result.get('created_at', '') }" rel="tooltip" data-placement="left" title="${ result.get('created_at', '') }"></a>
-          </small>
-          <a target="_blank" href="https://twitter.com/${ result.get('user_screen_name', '') }" class="account-group">
-            <img src="http://twitter.com/api/users/profile_image/${ result.get('user_screen_name', '') }" class="avatar"
-                 data-placement="left" rel="popover"  data-content="Location: ${ result.get('user_location', '') }
-             <br/>User tweets #: ${ result.get('user_statuses_count', '') }
-             <br/>User followers #: ${ result.get('user_followers_count', '') }" title="@${ result.get('user_screen_name', '') }" data-trigger="hover">
-            <strong class="fullname">${ result.get('user_name', '') }</strong>
-            <span>&rlm;</span><span class="username">@${ result.get('user_screen_name', '') }</span>
-          </a>
-        </div>
-        <div class="text" data-link="https://twitter.com/${ result.get('user_screen_name', '') }/status/${ result.get('id', '') }">
-          ${ parseLinks(result.get('text', ''))  | n,unicode }
-          %if result.get('retweet_count', ''):
-              <div class="retweeted">
-                ${_('Retweeted %s times') % result.get('retweet_count', '') }
-              </div>
-          %endif
-        </div>
-
-        <div class="stream-item-footer">
-          <ul class="tweet-actions">
-            <li class="action">
-              <a href="https://twitter.com/intent/tweet?in_reply_to=${ result.get('id', '') }" target="_blank">
-                <i class="icon icon-reply"></i>
-                <b>${_('Reply')}</b>
-              </a>
-            </li>
-            <li class="action">
-              <a href="https://twitter.com/intent/retweet?tweet_id=${ result.get('id', '') }" target="_blank">
-                <i class="icon icon-retweet"></i>
-                <b>${_('Retweet')}</b>
-              </a>
-            </li>
-          </ul>
-        </div>
+  <td style="word-wrap: break-word;">
+    <div class="content">
+      <div class="stream-item-header">
+        <small class="time">
+          <a href="https://twitter.com/${ result.get('user_screen_name', '') }/status/${ result.get('id', '') }" target="_blank" data-dt="${ result.get('created_at', '') }" rel="tooltip" data-placement="left" title="${ result.get('created_at', '') }"></a>
+        </small>
+        <a target="_blank" href="https://twitter.com/${ result.get('user_screen_name', '') }" class="account-group">
+          <img src="http://twitter.com/api/users/profile_image/${ result.get('user_screen_name', '') }" class="avatar"
+              data-placement="left" rel="popover"  data-content="Location: ${ result.get('user_location', '') }
+          <br/>User tweets #: ${ result.get('user_statuses_count', '') }
+           <br/>User followers #: ${ result.get('user_followers_count', '') }" title="@${ result.get('user_screen_name', '') }" data-trigger="hover">
+          <strong class="fullname">${ result.get('user_name', '') }</strong>
+         <span>&rlm;</span><span class="username">@${ result.get('user_screen_name', '') }</span>
+        </a>
+      </div>
+      <div class="text" data-link="https://twitter.com/${ result.get('user_screen_name', '') }/status/${ result.get('id', '') }">
+        ${ parseLinks(result.get('text', ''))  | n,unicode }
+        %if result.get('retweet_count', ''):
+          <div class="retweeted">
+            ${_('Retweeted %s times') % result.get('retweet_count', '') }
+          </div>
+        %endif
+      </div>
+
+      <div class="stream-item-footer">
+        <ul class="tweet-actions">
+          <li class="action">
+            <a href="https://twitter.com/intent/tweet?in_reply_to=${ result.get('id', '') }" target="_blank">
+              <i class="icon icon-reply"></i>
+              <b>${_('Reply')}</b>
+            </a>
+          </li>
+          <li class="action">
+            <a href="https://twitter.com/intent/retweet?tweet_id=${ result.get('id', '') }" target="_blank">
+              <i class="icon icon-retweet"></i>
+              <b>${_('Retweet')}</b>
+            </a>
+          </li>
+        </ul>
       </div>
     </div>
-    </td>
+   </div>
+  </td>
 </tr>
 </%def>

+ 53 - 0
apps/search/src/search/templates/navigation_bar_admin.mako

@@ -0,0 +1,53 @@
+## 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 django.utils.translation import ugettext as _
+%>
+
+<%namespace name="utils" file="utils.inc.mako" />
+
+
+<%def name="menubar(section='')">
+  <ul class="nav nav-tabs">
+    <li class="${ utils.is_selected(section, 'search') }">
+      <a href="${ url('search:index') }">${ _('Search') }</a>
+    </li>
+    <li class="${ utils.is_selected(section, 'cores') }">
+      <a href="${ url('search:admin_cores') }">${ _('Cores') }</a>
+    </li>
+    <li class="${ utils.is_selected(section, 'settings') }">
+      <a href="${ url('search:admin_settings') }">${ _('Settings') }</a>
+    </li>
+  </ul>
+</%def>
+
+<%def name="sub_menubar(core, section='')">
+  <ul class="nav nav-pills">
+    <li class="${ utils.is_selected(section, 'properties') }">
+      <a href="${ url('search:admin_core', core=core) }">${ _('Properties') }</a>
+    </li>
+    <li class="${ utils.is_selected(section, 'result') }">
+      <a href="${ url('search:admin_core_result', core=core) }">${ _('Result') }</a>
+    </li>
+    <li class="${ utils.is_selected(section, 'facets') }">
+      <a href="${ url('search:admin_core_facets', core=core) }">${ _('Facets') }</a>
+    </li>
+    <li class="${ utils.is_selected(section, 'sorting') }">
+      <a href="${ url('search:admin_core_sorting', core=core) }">${ _('Sorting') }</a>
+    </li>
+  </ul>
+</%def>

+ 24 - 0
apps/search/src/search/templates/utils.inc.mako

@@ -0,0 +1,24 @@
+## 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.
+
+<%def name="is_selected(section, matcher)">
+  <%
+    if section == matcher:
+      return "active"
+    else:
+      return ""
+  %>
+</%def>

+ 8 - 0
apps/search/src/search/urls.py

@@ -20,4 +20,12 @@ from django.conf.urls.defaults import patterns, url
 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/settings$', 'admin_settings', name='admin_settings'),
+  url(r'^admin/core/(?P<core>\w+)/settings$', 'admin_core', name='admin_core'),
+  url(r'^admin/core/(?P<core>\w+)/result$', 'admin_core_result', name='admin_core_result'),
+  url(r'^admin/core/(?P<core>\w+)/facets$', 'admin_core_facets', name='admin_core_facets'),
+  url(r'^admin/core/(?P<core>\w+)/sorting$', 'admin_core_sorting', name='admin_core_sorting'),
+
 )

+ 94 - 55
apps/search/src/search/views.py

@@ -19,79 +19,118 @@ try:
   import json
 except ImportError:
   import simplejson as json
+
 import logging
 
+from django.utils.translation import ugettext as _
+
 from desktop.lib.django_util import render
-from desktop.lib.exceptions_renderable import PopupException
-from desktop.lib.rest.http_client import HttpClient, RestException
-from desktop.lib.rest.resource import Resource
 
+from search.api import SolrApi
+from search.conf import SOLR_URL
+from search.decorators import allow_admin_only
 from search.forms import QueryForm
+from search.models import Core, temp_fixture_hook
 
 
-# http://lucene.apache.org/solr/api-4_0_0-BETA/doc-files/tutorial.html#Getting+Started
-SOLR_URL = 'http://c1328.halxg.cloudera.com:8983/solr/'
-
 LOG = logging.getLogger(__name__)
 
 
 def index(request):
+  temp_fixture_hook()
+
   search_form = QueryForm(request.GET)
   response = {}
+  solr_query = {}
 
   if search_form.is_valid():
-    solr_query = {}
+    core = search_form.cleaned_data['cores']
+    solr_query['core'] = core
     solr_query['q'] = search_form.cleaned_data['query']
     solr_query['fq'] = search_form.cleaned_data['fq']
     solr_query['sort'] = search_form.cleaned_data['sort'] or 'created_at desc'
     solr_query['rows'] = search_form.cleaned_data['rows'] or 15
     solr_query['start'] = search_form.cleaned_data['start'] or 0
     solr_query['facets'] = search_form.cleaned_data['facets'] or 1
-    response = SolrApi(SOLR_URL).query(solr_query)
-    response = json.loads(response)
-
-  return render('index.mako', request, {'search_form': search_form, 'response': response, 'rr': json.dumps(response), 'solr_query': solr_query})
-
-# Simple API for now
-class SolrApi(object):
-  def __init__(self, solr_url):
-    self._url = solr_url
-    self._client = HttpClient(self._url, logger=LOG)
-    self._root = Resource(self._client)
-
-  def query(self, solr_query):
-    try:
-      params = (('q', solr_query['q']),
-                ('wt', 'json'),
-                ('sort', solr_query['sort']),
-                ('rows', solr_query['rows']),
-                ('start', solr_query['start']),
-
-                ('facet', solr_query['facets'] == 1 and 'true' or 'false'),
-                ('facet.limit', 10),
-                ('facet.mincount', 1),
-                ('facet.sort', 'count'),
-
-                ('facet.field', 'user_location'),
-                ('facet.field', 'user_statuses_count'),
-                ('facet.field', 'user_followers_count'),
-
-                ('facet.range', 'retweet_count'),
-                ('f.retweet_count.facet.range.start', '0'),
-                ('f.retweet_count.facet.range.end', '100'),
-                ('f.retweet_count.facet.range.gap', '10'),
-
-                ('facet.date', 'created_at'),
-                ('facet.date.start', 'NOW/DAY-305DAYS'),
-                ('facet.date.end', 'NOW/DAY+1DAY'),
-                ('facet.date.gap', '+1DAY'),
-             )
-
-      fqs = solr_query['fq'].split('|')
-      for fq in fqs:
-        if fq:
-          params += (('fq', fq),)
-
-      return self._root.get('collection1/browse', params)
-    except RestException, e:
-      raise PopupException('Error while accessing Solr: %s' % e)
+
+    hue_core = Core.objects.get(name=core)
+
+    response = SolrApi(SOLR_URL.get()).query(solr_query, hue_core)
+
+  return render('index.mako', request, {
+                    'search_form': search_form,
+                    'response': response,
+                    'solr_query': solr_query,
+                    'hue_core': hue_core,
+                    'rr': json.dumps(response),
+                })
+
+
+@allow_admin_only
+def admin(request):
+  # To cross check both
+  cores = SolrApi(SOLR_URL.get()).cores()
+  hue_cores = Core.objects.all()
+
+  return render('admin.mako', request, {
+                    'cores': cores,
+                    'hue_cores': hue_cores,
+                })
+
+@allow_admin_only
+def admin_settings(request):
+  cores = SolrApi(SOLR_URL.get()).cores()
+  hue_cores = Core.objects.all()
+
+  return render('admin_settings.mako', request, {
+                    'cores': cores,
+                    'hue_cores': hue_cores,
+                })
+
+@allow_admin_only
+def admin_core(request, core):
+  solr_core = SolrApi(SOLR_URL.get()).core(core)
+  solr_schema = SolrApi(SOLR_URL.get()).schema(core)
+  hue_core = Core.objects.get(name=core)
+
+  return render('admin_core.mako', request, {
+                    'solr_core': solr_core,
+                    'solr_schema': solr_schema,
+                    'hue_core': hue_core,
+                })
+
+@allow_admin_only
+def admin_core_result(request, core):
+  solr_core = SolrApi(SOLR_URL.get()).core(core)
+  hue_core = Core.objects.get(name=core)
+
+  return render('admin_core_result.mako', request, {
+                    'solr_core': solr_core,
+                    'hue_core': hue_core,
+                })
+
+@allow_admin_only
+def admin_core_facets(request, core):
+  solr_core = SolrApi(SOLR_URL.get()).core(core)
+  hue_core = Core.objects.get(name=core)
+
+  if request.method == 'POST':
+    print request.POST
+    hue_core.facets.update_from_post(request.POST)
+    hue_core.facets.save()
+    request.info(_('Facets updated'))
+
+  return render('admin_core_facets.mako', request, {
+                    'solr_core': solr_core,
+                    'hue_core': hue_core,
+                })
+
+@allow_admin_only
+def admin_core_sorting(request, core):
+  solr_core = SolrApi(SOLR_URL.get()).core(core)
+  hue_core = Core.objects.get(name=core)
+
+  return render('admin_core_sorting.mako', request, {
+                    'solr_core': solr_core,
+                    'hue_core': hue_core,
+                })

+ 10 - 0
desktop/conf.dist/hue.ini

@@ -472,6 +472,16 @@
   ## impala_principal=impala/hostname.foo.com
 
 
+###########################################################################
+# Settings to configure Solr Search
+###########################################################################
+
+[search]
+
+   # URL of the Solr Server
+   ## solr_url=http://localhost:1978/solr/
+
+
 ###########################################################################
 # Settings to configure Job Designer
 ###########################################################################

+ 10 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -475,6 +475,16 @@
   ## impala_principal=impala/hostname.foo.com
 
 
+###########################################################################
+# Settings to configure Solr Search
+###########################################################################
+
+[search]
+
+   # URL of the Solr Server
+   ## solr_url=http://localhost:1978/solr/
+
+
 ###########################################################################
 # Settings to configure Job Designer
 ###########################################################################