瀏覽代碼

[search] Move Collection model to Document2 model

Romain Rigaux 10 年之前
父節點
當前提交
772b3c6184

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

@@ -205,6 +205,7 @@ class Sorting(models.Model):
     return params
 
 
+# Deprecated
 class CollectionManager(models.Manager):
 
   def create2(self, name, label, is_core_only=False, owner=None):
@@ -227,6 +228,7 @@ class CollectionManager(models.Manager):
     return collection
 
 
+# Deprecated see Collection2
 class Collection(models.Model):
   """All the data is now saved into the properties field"""
   enabled = models.BooleanField(default=False) # Aka shared
@@ -433,6 +435,138 @@ class Collection(models.Model):
           })
 
 
+class Collection2(object):
+
+  def __init__(self, user, name='Default', data=None, document=None):
+    self.document = document
+
+    if document is not None:
+      self.data = json.loads(document.data)
+    elif data is not None:
+      self.data = json.loads(data)
+    else:
+      self.data = {
+          'collection': self.get_default(user, name),
+          'layout': []
+      }
+
+  def get_c(self, user):
+    props = self.data
+
+    if self.document is not None:
+      props['collection']['id'] = self.document.id
+
+    # For backward compatibility
+    if 'rows' not in props['collection']['template']:
+      props['collection']['template']['rows'] = 10
+    if 'enabled' not in props['collection']:
+      props['collection']['enabled'] = True
+    if 'leafletmap' not in props['collection']['template']:
+      props['collection']['template']['leafletmap'] = {'latitudeField': None, 'longitudeField': None, 'labelField': None}
+
+    for facet in props['collection']['facets']:
+      properties = facet['properties']
+      if 'gap' in properties and not 'initial_gap' in properties:
+        properties['initial_gap'] = properties['gap']
+      if 'start' in properties and not 'initial_start' in properties:
+        properties['initial_start'] = properties['start']
+      if 'end' in properties and not 'initial_end' in properties:
+        properties['initial_end'] = properties['end']
+
+      if facet['widgetType'] == 'histogram-widget':
+        if 'timelineChartType' not in properties:
+          properties['timelineChartType'] = 'bar'
+        if 'extraSeries' not in properties:
+          properties['extraSeries'] = []
+
+      if facet['widgetType'] == 'map-widget' and facet['type'] == 'field':
+        facet['type'] = 'pivot'
+        properties['facets'] = []
+        properties['facets_form'] = {'field': '', 'mincount': 1, 'limit': 5}
+
+    return json.dumps(props)
+
+  def get_default(self, user, name):
+    fields = self.fields_data(user, name)
+    id_field = [field['name'] for field in fields if field.get('isId')]
+    if id_field:
+      id_field = id_field[0]
+
+    TEMPLATE = {
+      "extracode": escape("<style type=\"text/css\">\nem {\n  font-weight: bold;\n  background-color: yellow;\n}</style>\n\n<script>\n</script>"),
+      "highlighting": [""],
+      "properties": {"highlighting_enabled": True},
+      "template": """
+      <div class="row-fluid">
+        <div class="row-fluid">
+          <div class="span12">%s</div>
+        </div>
+        <br/>
+      </div>""" % ' '.join(['{{%s}}' % field['name'] for field in fields]),
+      "isGridLayout": True,
+      "showFieldList": True,
+      "fieldsAttributes": [self._make_gridlayout_header_field(field) for field in fields],
+      "fieldsSelected": [],
+      "leafletmap": {'latitudeField': None, 'longitudeField': None, 'labelField': None},
+      "rows": 10,
+    }
+
+    FACETS = []
+
+    return {
+      'id': None,
+      'name': name,
+      'label': name,
+      'enabled': False,
+      'template': TEMPLATE,
+      'facets': FACETS,
+      'fields': fields,
+      'idField': id_field,
+    }
+
+  @classmethod
+  def _make_field(cls, field, attributes):
+    return {
+        'name': str(field),
+        'type': str(attributes.get('type', '')),
+        'isId': attributes.get('required') and attributes.get('uniqueKey'),
+        'isDynamic': 'dynamicBase' in attributes
+    }
+
+  @classmethod
+  def _make_gridlayout_header_field(cls, field, isDynamic=False):
+    return {'name': field['name'], 'sort': {'direction': None}, 'isDynamic': isDynamic}
+
+  def get_absolute_url(self):
+    return reverse('search:index') + '?collection=%s' % self.id
+
+  def fields(self, user):
+    return sorted([str(field.get('name', '')) for field in self.fields_data(user)])
+
+  def fields_data(self, user, name):
+    schema_fields = SolrApi(SOLR_URL.get(), user).fields(name)
+    schema_fields = schema_fields['schema']['fields']
+
+    return sorted([self._make_field(field, attributes) for field, attributes in schema_fields.iteritems()])
+
+  def update_data(self, post_data):
+    data_dict = self.data
+
+    data_dict.update(post_data)
+
+    self.data = data_dict
+
+  @property
+  def autocomplete(self):
+    return self.data['autocomplete']
+
+  @autocomplete.setter
+  def autocomplete(self, autocomplete):
+    properties_ = self.data
+    properties_['autocomplete'] = autocomplete
+    self.data = json.dumps(properties_)
+
+
 def get_facet_field(category, field, facets):
   facets = filter(lambda facet: facet['type'] == category and '%(field)s-%(id)s' % facet == field, facets)
   if facets:

+ 19 - 6
apps/search/src/search/search_controller.py

@@ -22,10 +22,10 @@ from django.contrib.auth.models import User
 from django.db.models import Q
 from django.utils.translation import ugettext as _
 
+from desktop.models import Document2
 from libsolr.api import SolrApi
 
 from search.conf import SOLR_URL
-from search.models import Collection
 
 
 LOG = logging.getLogger(__name__)
@@ -40,22 +40,33 @@ class SearchController(object):
 
   def get_search_collections(self):
     if self.user.is_superuser:
-      return Collection.objects.all().order_by('-id')
+      return Document2.objects.filter(type='search-dashboard').order_by('-id')
     else:
-      return Collection.objects.filter(Q(owner=self.user) | Q(enabled=True)).order_by('-id')
+      return Document2.objects.filter(type='search-dashboard').filter(owner=self.user).order_by('-id')
 
   def get_shared_search_collections(self):
-    return Collection.objects.filter(Q(owner=self.user) | Q(enabled=True, owner__in=User.objects.filter(is_superuser=True)) | Q(id__in=[20000000, 20000001, 20000002, 20000003])).order_by('-id')
+    return Document2.objects.filter(type='search-dashboard').filter(Q(owner=self.user) | Q(owner__in=User.objects.filter(is_superuser=True)) | Q(id__in=[20000000, 20000001, 20000002])).order_by('-id')
 
   def get_owner_search_collections(self):
     if self.user.is_superuser:
-      return Collection.objects.all()
+      return Document2.objects.filter(type='search-dashboard')
     else:
-      return Collection.objects.filter(Q(owner=self.user))
+      return Document2.objects.filter(type='search-dashboard').filter(Q(owner=self.user))
+
+  def get_icon(self, name):
+    if name == 'twitter_demo':
+      return 'search/art/icon_twitter_48.png'
+    elif name == 'yelp_demo':
+      return 'search/art/icon_yelp_48.png'
+    elif name == 'log_analytics_demo':
+      return 'search/art/icon_logs_48.png'
+    else:
+      return 'search/art/icon_search_48.png'
 
   def delete_collections(self, collection_ids):
     result = {'status': -1, 'message': ''}
     try:
+      # todo
       self.get_owner_search_collections().filter(id__in=collection_ids).delete()
       result['status'] = 0
     except Exception, e:
@@ -72,6 +83,8 @@ class SearchController(object):
         copy.label += _(' (Copy)')
         copy.id = copy.pk = None
 
+        # todo
+
         facets = copy.facets
         facets.id = None
         facets.save()

+ 2 - 0
apps/search/src/search/static/search/js/search.ko.js

@@ -52,6 +52,7 @@ function loadLayout(viewModel, json_layout) {
 var Query = function (vm, query) {
   var self = this;
 
+  self.uuid = ko.observable(typeof query.uuid != "undefined" && query.uuid != null ? query.uuid : UUID());
   self.qs = ko.mapping.fromJS(query.qs);
   self.fqs = ko.mapping.fromJS(query.fqs);
   self.start = ko.mapping.fromJS(query.start);
@@ -393,6 +394,7 @@ var Collection = function (vm, collection) {
   var self = this;
 
   self.id = ko.mapping.fromJS(collection.id);
+  self.uuid = ko.observable(typeof collection.uuid != "undefined" && collection.uuid != null ? collection.uuid : UUID());
   self.name = ko.mapping.fromJS(collection.name);
   self.label = ko.mapping.fromJS(collection.label);
   self.enabled = ko.mapping.fromJS(collection.enabled);

+ 1 - 3
apps/search/src/search/templates/admin_collections.mako

@@ -76,12 +76,11 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
           <thead>
             <tr>
               <th style="width: 1%">
-                <span data-bind="click: toggleSelectAll, css: {'fa-check': !ko.utils.arrayFilter(filteredCollections(), function(collection) {return !collection.selected()}).length}" class="hueCheckbox fa"></span>
+                <span data-bind="click: toggleSelectAll, css: {'fa-check': ! ko.utils.arrayFilter(filteredCollections(), function(collection) {return !collection.selected()}).length}" class="hueCheckbox fa"></span>
               </th>
               <th>${ _('Name') }</th>
               <th>${ _('Solr Index') }</th>
               <th width="15%">${ _('Owner') }</th>
-              <th width="1%" class="center">${ _('Shared') }</th>
             </tr>
           </thead>
           <tbody data-bind="foreach: filteredCollections">
@@ -92,7 +91,6 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
               <td><a data-bind="text: label, click: $root.editCollection" title="${ _('Click to edit') }" class="pointer"></a></td>
               <td><a data-bind="text: name, click: $root.editIndex" title="${ _('Click to edit the index') }" class="pointer"></a></td>
               <td><span data-bind="text: owner"></span></td>
-              <td class="center"><span data-bind="css: { 'fa fa-check': enabled }"></span></td>
             </tr>
           </tbody>
         </table>

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

@@ -25,6 +25,7 @@ from django.utils.translation import ugettext as _
 from desktop.lib.django_util import JsonResponse, render
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.rest.http_client import RestException
+from desktop.models import Document2, Document
 
 from libsolr.api import SolrApi
 from indexer.management.commands import indexer_setup
@@ -34,7 +35,7 @@ from search.conf import SOLR_URL
 from search.data_export import download as export_download
 from search.decorators import allow_owner_only, allow_viewer_only
 from search.management.commands import search_setup
-from search.models import Collection, augment_solr_response, augment_solr_exception, pairwise2
+from search.models import Collection2, augment_solr_response, augment_solr_exception, pairwise2
 from search.search_controller import SearchController
 
 
@@ -49,7 +50,8 @@ def index(request):
     return admin_collections(request, True)
 
   try:
-    collection = hue_collections.get(id=collection_id)
+    collection_doc = hue_collections.get(id=collection_id)
+    collection = Collection2(request.user, document=collection_doc)
   except Exception, e:
     raise PopupException(e, title=_("Dashboard does not exist or you don't have the permission to access it."))
 
@@ -59,7 +61,7 @@ def index(request):
     'collection': collection,
     'query': query,
     'initial': json.dumps({'collections': [], 'layout': []}),
-    'is_owner': request.user == collection.owner
+    'is_owner': request.user == collection_doc.owner
   })
 
 
@@ -68,7 +70,7 @@ def new_search(request):
   if not collections:
     return no_collections(request)
 
-  collection = Collection(name=collections[0], label=collections[0])
+  collection = Collection2(user=request.user, name=collections[0])
   query = {'qs': [{'q': ''}], 'fqs': [], 'start': 0}
 
   return render('search.mako', request, {
@@ -96,7 +98,7 @@ def browse(request, name):
   if not collections:
     return no_collections(request)
 
-  collection = Collection(name=name, label=name)
+  collection = Collection2(user=request.user, name=name)
   query = {'qs': [{'q': ''}], 'fqs': [], 'start': 0}
 
   return render('search.mako', request, {
@@ -157,17 +159,21 @@ def save(request):
 
   if collection:
     if collection['id']:
-      hue_collection = Collection.objects.get(id=collection['id'])
+      dashboard_doc = Document2.objects.get(id=collection['id'])
     else:
-      hue_collection = Collection.objects.create2(name=collection['name'], label=collection['label'], owner=request.user)
-    hue_collection.update_properties({'collection': collection})
-    hue_collection.update_properties({'layout': layout})
-    hue_collection.name = collection['name']
-    hue_collection.label = collection['label']
-    hue_collection.enabled = collection['enabled']
-    hue_collection.save()
+      dashboard_doc = Document2.objects.create(name=collection['name'], uuid=collection['uuid'], type='search-dashboard', owner=request.user, description=collection['label'])
+      Document.objects.link(dashboard_doc, owner=request.user, name=collection['name'], description=collection['label'], extra='search-dashboard')
+
+    dashboard_doc.update_data({
+        'collection': collection,
+        'layout': layout
+    })
+    dashboard_doc.name = collection['name']
+    dashboard_doc.description = collection['label']
+    dashboard_doc.save()
+    
     response['status'] = 0
-    response['id'] = hue_collection.id
+    response['id'] = dashboard_doc.id
     response['message'] = _('Page saved !')
   else:
     response['message'] = _('There is no collection to search.')
@@ -206,9 +212,7 @@ def admin_collections(request, is_redirect=False):
       massaged_collection = {
         'id': collection.id,
         'name': collection.name,
-        'label': collection.label,
-        'enabled': collection.enabled,
-        'isCoreOnly': collection.is_core_only,
+        'label': collection.description,
         'absoluteUrl': collection.get_absolute_url(),
         'owner': collection.owner and collection.owner.username,
         'isOwner': collection.owner == request.user or request.user.is_superuser

+ 21 - 0
desktop/core/src/desktop/models.py

@@ -325,6 +325,23 @@ class DocumentManager(models.Manager):
     except Exception, e:
       LOG.warn(force_unicode(e))
 
+    try:
+      with transaction.atomic():
+        from search.models import Collection
+
+        for dashboard in Collection.objects.all():
+          if not dashboard.doc.exists(): # not dashbord uuid?   
+            # if brand new
+            # if 3.7, 3.8
+            # if 3.6
+            data = ''         
+            dashboard_doc = Document2.objects.create(name=dashboard.name, uuid=str(uuid.uuid4()), type='search-dashboard', owner=dashboard.owner, description=dashboard.label)
+            Document.objects.link(dashboard_doc, owner=dashboard.owner, name=dashboard.name, description=dashboard.label, extra='search-dashboard')
+            # set uuid
+    
+    except Exception, e:
+      LOG.warn(force_unicode(e))
+
     try:
       with transaction.atomic():
         for job in Document2.objects.all():
@@ -341,6 +358,8 @@ class DocumentManager(models.Manager):
               extra = 'bundle2'
             elif job.type == 'notebook':
               extra = 'notebook'
+            elif job.type == 'search-dashboard':
+              extra = 'search-dashboard'
             else:
               extra = ''
             doc = Document.objects.link(job, owner=job.owner, name=job.name, description=job.description, extra=extra)
@@ -703,6 +722,8 @@ class Document2(models.Model):
       return reverse('oozie:edit_bundle') + '?bundle=' + str(self.id)
     elif self.type == 'notebook':
       return reverse('spark:editor') + '?notebook=' + str(self.id)
+    elif self.type == 'search-dashboard':
+      return reverse('search:index') + '?collection=' + str(self.id)
     else:
       return reverse('oozie:edit_workflow') + '?workflow=' + str(self.id)
 

+ 10 - 3
desktop/core/src/desktop/templates/common_header.mako

@@ -491,17 +491,24 @@ from django.utils.translation import ugettext as _
        % endif
        % if 'search' in apps:
          <% from search.search_controller import SearchController %>
-         <% collections = SearchController(user).get_shared_search_collections() %>
+         <% controller = SearchController(user) %>
+         <% collections = controller.get_shared_search_collections() %>
          % if not collections:
            <li>
              <a title="${_('Solr Search')}" rel="navigator-tooltip" href="${ url('search:index') }">Search</a>
            </li>
          % else:
            <li class="dropdown">
-             <a title="${_('Solr Search')}" rel="navigator-tooltip" href="#" data-toggle="dropdown" class="dropdown-toggle">${_('Search')} <b class="caret"></b></a>
+             <a title="${_('Solr Search')}" rel="navigator-tooltip" href="#" data-toggle="dropdown" class="dropdown-toggle">
+               ${_('Search')} <b class="caret"></b>
+             </a>
              <ul role="menu" class="dropdown-menu">
                % for collection in collections:
-                 <li><a href="${ url('search:index') }?collection=${ collection.id }"><img src="${ static(collection.icon) }" class="app-icon"/> ${ collection.label }</a></li>
+                 <li>
+                   <a href="${ url('search:index') }?collection=${ collection.id }">
+                     <img src="${ static(controller.get_icon(collection.name)) }" class="app-icon"/> ${ collection.name }
+                   </a>
+                 </li>
                % endfor
                % if 'indexer' in apps or 'search' in apps:
                  <li class="divider"></li>