Pārlūkot izejas kodu

HUE-1708 [search] Allow search results to be exported

Download as CSV or XLS.
Generalize a couple of methods that overlap.
Download in a different window.
Abraham Elmahrek 11 gadi atpakaļ
vecāks
revīzija
7aa96e7957

+ 69 - 0
apps/search/src/search/data_export.py

@@ -0,0 +1,69 @@
+#!/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.
+#
+# Handling of data export
+
+import logging
+
+from django.utils.encoding import smart_str
+
+from desktop.lib import export_csvxls
+
+
+LOG = logging.getLogger(__name__)
+DL_FORMATS = [ 'csv', 'xls' ]
+
+
+def download(results, format):
+  """
+  download(results, format) -> HttpResponse
+
+  Transform the search result set to the specified format and dwonload.
+  """
+  if format not in DL_FORMATS:
+    LOG.error('Unknown download format "%s"' % format)
+    return
+
+  data = SearchDataAdapter(results, format)
+  return export_csvxls.make_response(data[0], data[1:], format, 'query_result')
+
+
+def SearchDataAdapter(results, format):
+  """
+  SearchDataAdapter(results, format, db) -> 2D array of data.
+
+  First line should be the headers.
+  """
+  if results and results['response'] and results['response']['docs']:
+    search_data = results['response']['docs']
+    order = search_data[0].keys()
+    rows = [order]
+
+    for data in search_data:
+      row = []
+      for column in order:
+        if column not in data:
+          row.append("")
+        elif isinstance(data[column], basestring) or isinstance(data[column], (int, long, float, complex)):
+          row.append(data[column])
+        else:
+          row.append(smart_str(data[column]))
+      rows.append(row)
+  else:
+    rows = [[]]
+
+  return rows

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

@@ -16,6 +16,8 @@
 # limitations under the License.
 
 
+import math
+
 from django import forms
 from django.utils.translation import ugettext as _
 from search.models import Collection
@@ -46,6 +48,25 @@ class QueryForm(forms.Form):
     else:
       return self.initial_collection
 
+  @property
+  def solr_query_dict(self):
+    solr_query = {}
+
+    if self.is_valid():
+      solr_query['q'] = self.cleaned_data['query'].encode('utf8')
+      solr_query['fq'] = self.cleaned_data['fq']
+      if self.cleaned_data['sort']:
+        solr_query['sort'] = self.cleaned_data['sort']
+      solr_query['rows'] = self.cleaned_data['rows'] or 15
+      solr_query['start'] = self.cleaned_data['start'] or 0
+      solr_query['facets'] = self.cleaned_data['facets'] or 1
+      solr_query['current_page'] = int(math.ceil((float(solr_query['start']) + 1) / float(solr_query['rows'])))
+      solr_query['total_pages'] = 0
+      solr_query['search_time'] = 0
+      solr_query['collection'] = Collection.objects.get(id=self.cleaned_data['collection']).name
+    
+    return solr_query
+
 
 class HighlightingForm(forms.Form):
   fields = forms.MultipleChoiceField(required=False)

+ 26 - 1
apps/search/src/search/templates/search.mako

@@ -79,7 +79,17 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
       </div>
 
       ${ search_form | n,unicode }
-      <button type="submit" class="btn btn-inverse"><i class="fa fa-search"></i></button>
+      <button type="submit" id="search-btn" class="btn btn-inverse"><i class="fa fa-search"></i></button>
+
+      % if response and 'response' in response and 'docs' in response['response'] and len(response['response']['docs']) > 0:
+      <div class="btn-group download-btn-group" style="margin-left: 15px">
+        <button type="button" id="download-btn" class="btn btn-inverse dropdown-toggle" data-toggle="dropdown"><i class="fa fa-download"></i></button>
+        <ul class="dropdown-menu" role="menu">
+          <li><a href="javascript:void(0)" id="download-csv"><i class="fa fa-list"></i>&nbsp; CSV</a></li>
+          <li><a href="javascript:void(0)" id="download-xls"><i class="fa fa-th"></i>&nbsp; XLS</a></li>
+        </ul>
+      </div>
+      % endif
     </div>
   </form>
 </div>
@@ -417,6 +427,21 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
       $(".selectMask").popover("hide");
     });
 
+    $("#download-csv").on("click", function(e) {
+      $("form").attr('action', "${ url('search:download', format='csv') }");
+      $("form").attr('target', "_new");
+      $("form").submit();
+      $("form").removeAttr('action');
+      $("form").removeAttr('target');
+    });
+    $("#download-xls").on("click", function(e) {
+      $("form").attr('action', "${ url('search:download', format='xls') }");
+      $("form").attr('target', "_new");
+      $("form").submit();
+      $("form").removeAttr('action');
+      $("form").removeAttr('target');
+    });
+
     function getCollectionPopoverContent() {
       var _html = "<ul class='unstyled'>";
       $("#collectionPopover ul li").each(function () {

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 5 - 1
apps/search/src/search/tests.py


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

@@ -20,6 +20,7 @@ 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'^download/(?P<format>(csv|xls))$', 'download', name='download'),
 
   url(r'^admin/collections$', 'admin_collections', name='admin_collections'),
   url(r'^admin/collections_import$', 'admin_collections_import', name='admin_collections_import'),

+ 46 - 21
apps/search/src/search/views.py

@@ -21,6 +21,7 @@ import math
 
 from django.core.urlresolvers import reverse
 from django.http import HttpResponse
+from django.utils.encoding import smart_str
 from django.utils.translation import ugettext as _
 from django.shortcuts import redirect
 
@@ -29,8 +30,9 @@ from desktop.lib.exceptions_renderable import PopupException
 
 from search.api import SolrApi
 from search.conf import SOLR_URL
+from search.data_export import download as export_download
 from search.decorators import allow_admin_only
-from search.forms import QueryForm, CollectionForm, HighlightingForm
+from search.forms import QueryForm, CollectionForm
 from search.models import Collection, augment_solr_response
 from search.search_controller import SearchController
 
@@ -39,6 +41,16 @@ from django.utils.encoding import force_unicode
 LOG = logging.getLogger(__name__)
 
 
+def initial_collection(request, hue_collections):
+  initial_collection = request.COOKIES.get('hueSearchLastCollection', hue_collections[0].id)
+  try:
+    Collection.objects.get(id=initial_collection)
+  except:
+    initial_collection = hue_collections[0].id
+
+  return initial_collection
+
+
 def index(request):
   hue_collections = Collection.objects.filter(enabled=True)
 
@@ -48,35 +60,21 @@ def index(request):
     else:
       return no_collections(request)
 
-  initial_collection = request.COOKIES.get('hueSearchLastCollection', hue_collections[0].id)
-  try:
-    Collection.objects.get(id=initial_collection)
-  except Exception, e:
-    initial_collection = hue_collections[0].id
+  init_collection = initial_collection(request, hue_collections)
 
-  search_form = QueryForm(request.GET, initial_collection=initial_collection)
+  search_form = QueryForm(request.GET, initial_collection=init_collection)
   response = {}
   error = {}
   solr_query = {}
-  hue_collection = None
 
   if search_form.is_valid():
-    collection_id = search_form.cleaned_data['collection']
-    solr_query['q'] = search_form.cleaned_data['query'].encode('utf8')
-    solr_query['fq'] = search_form.cleaned_data['fq']
-    if search_form.cleaned_data['sort']:
-      solr_query['sort'] = search_form.cleaned_data['sort']
-    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
-    solr_query['current_page'] = int(math.ceil((float(solr_query['start']) + 1) / float(solr_query['rows'])))
-    solr_query['total_pages'] = 0
-    solr_query['search_time'] = 0
-
     try:
+      collection_id = search_form.cleaned_data['collection']
       hue_collection = Collection.objects.get(id=collection_id)
-      solr_query['collection'] = hue_collection.name
+
+      solr_query = search_form.solr_query_dict
       response = SolrApi(SOLR_URL.get(), request.user).query(solr_query, hue_collection)
+
       solr_query['total_pages'] = int(math.ceil((float(response['response']['numFound']) / float(solr_query['rows']))))
       solr_query['search_time'] = response['responseHeader']['QTime']
     except Exception, e:
@@ -103,6 +101,33 @@ def index(request):
   })
 
 
+def download(request, format):
+  hue_collections = Collection.objects.filter(enabled=True)
+
+  if not hue_collections:
+    raise PopupException(_("No collection to download."))
+
+  init_collection = initial_collection(request, hue_collections)
+
+  search_form = QueryForm(request.GET, initial_collection=init_collection)
+
+  if search_form.is_valid():
+    try:
+      collection_id = search_form.cleaned_data['collection']
+      hue_collection = Collection.objects.get(id=collection_id)
+
+      solr_query = search_form.solr_query_dict
+      response = SolrApi(SOLR_URL.get(), request.user).query(solr_query, hue_collection)
+
+      LOG.debug('Download results for query %s' % smart_str(solr_query))
+
+      return export_download(response, format)
+    except Exception, e:
+      raise PopupException(_("Could not download search results: %s") % e)
+  else:
+    raise PopupException(_("Could not download search results: %s") % search_form.errors)
+
+
 def no_collections(request):
   return render('no_collections.mako', request, {})
 

+ 8 - 0
apps/search/static/css/search.css

@@ -170,3 +170,11 @@ body {
   margin-left: 0 !important;
   border-left: 0 !important;
 }
+
+.download-btn-group {
+  min-width: auto;
+}
+
+.download-btn-group > ul > li {
+  text-align: left;
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels