浏览代码

HUE-1589 [search] Port old sharing permission system to document model

Romain Rigaux 10 年之前
父节点
当前提交
f732238

+ 1 - 1
apps/oozie/src/oozie/templates/editor2/list_editor_workflows.mako

@@ -213,7 +213,7 @@ ${ commonshare() | n,unicode }
     };
 
     self.prepareShareModal = function() {
-     shareViewModel.setDocId(self.selectedJobs()[0].doc1_id());
+      shareViewModel.setDocId(self.selectedJobs()[0].doc1_id());
       openShareModal();
     };
   }

+ 9 - 12
apps/search/src/search/decorators.py

@@ -22,9 +22,7 @@ from django.utils.functional import wraps
 from django.utils.translation import ugettext as _
 
 from desktop.lib.exceptions_renderable import PopupException
-
-from search.models import Collection
-from search.search_controller import SearchController
+from desktop.models import Document2
 
 
 LOG = logging.getLogger(__name__)
@@ -37,8 +35,9 @@ def allow_viewer_only(view_func):
 
     if collection_json['id']:
       try:
-        SearchController(request.user).get_search_collections().get(id=collection_json['id'])
-      except Collection.DoesNotExist:
+        doc2 = Document2.objects.get(id=collection_json['id'])
+        doc2.doc.get().can_read_or_exception(request.user)
+      except Document2.DoesNotExist:
         message = _("Dashboard does not exist or you don't have the permission to access it.")
         raise PopupException(message)
 
@@ -53,13 +52,11 @@ def allow_owner_only(view_func):
 
     if collection_json['id']:
       try:
-        collection = Collection.objects.get(id=collection_json['id'])
-
-        if collection.owner != request.user and not request.user.is_superuser:
-          message = _("Permission denied. You are not an Administrator.")
-          raise PopupException(message)
-      except Collection.DoesNotExist:
-        pass
+        doc2 = Document2.objects.get(id=collection_json['id'])
+        doc2.doc.get().can_write_or_exception(request.user)
+      except Document2.DoesNotExist:
+        message = _("Dashboard does not exist or you don't have the permission to access it.")
+        raise PopupException(message)
 
     return view_func(request, *args, **kwargs)
   return wraps(view_func)(decorate)

+ 37 - 36
apps/search/src/search/search_controller.py

@@ -19,11 +19,9 @@
 import logging
 import uuid
 
-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, SAMPLE_USERNAME
+from desktop.models import Document2, Document, SAMPLE_USERNAME
 from libsolr.api import SolrApi
 
 from search.conf import SOLR_URL
@@ -41,19 +39,21 @@ class SearchController(object):
     self.user = user
 
   def get_search_collections(self):
-    if self.user.is_superuser:
-      return Document2.objects.filter(type='search-dashboard').order_by('-id')
-    else:
-      return Document2.objects.filter(type='search-dashboard').filter(owner=self.user).order_by('-id')
+    return [d.content_object for d in Document.objects.get_docs(self.user, Document2, extra='search-dashboard').order_by('-id')]
 
   def get_shared_search_collections(self):
-    return Document2.objects.filter(type='search-dashboard').filter(Q(owner=self.user) | Q(owner__in=User.objects.filter(is_superuser=True)) | Q(owner__username=SAMPLE_USERNAME)).order_by('-id')
+    # Those are the ones appearing in the menu
+    docs = Document.objects.filter(Q(owner=self.user) | Q(owner__username=SAMPLE_USERNAME), extra='search-dashboard')
+
+    return [d.content_object for d in docs.order_by('-id')]
 
   def get_owner_search_collections(self):
     if self.user.is_superuser:
-      return Document2.objects.filter(type='search-dashboard')
+      docs = Document.objects.filter(extra='search-dashboard')
     else:
-      return Document2.objects.filter(type='search-dashboard').filter(Q(owner=self.user))
+      docs = Document.objects.filter(extra='search-dashboard', owner=self.user)
+
+    return [d.content_object for d in docs.order_by('-id')]
 
   def get_icon(self, name):
     if name == 'Twitter':
@@ -68,10 +68,11 @@ class SearchController(object):
   def delete_collections(self, collection_ids):
     result = {'status': -1, 'message': ''}
     try:
-      for doc2 in self.get_owner_search_collections().filter(id__in=collection_ids):
-        doc = doc2.doc.get()
-        doc.delete()
-        doc2.delete()
+      for doc2 in self.get_owner_search_collections():
+        if doc2.id in collection_ids:
+          doc = doc2.doc.get()
+          doc.delete()
+          doc2.delete()
       result['status'] = 0
     except Exception, e:
       LOG.warn('Error deleting collection: %s' % e)
@@ -82,30 +83,30 @@ class SearchController(object):
   def copy_collections(self, collection_ids):
     result = {'status': -1, 'message': ''}
     try:
-      for collection in self.get_shared_search_collections().filter(id__in=collection_ids):
-        doc2 = Document2.objects.get(type='search-dashboard', id=collection.id)
-
-        name = doc2.name + '-copy'
-        copy_doc = doc2.doc.get().copy(name=name, owner=self.user)
-
-        doc2.pk = None
-        doc2.id = None
-        doc2.uuid = str(uuid.uuid4())
-        doc2.name = name
-        doc2.owner = self.user
-        doc2.save()
-
-        doc2.doc.all().delete()
-        doc2.doc.add(copy_doc)
-        doc2.save()
-
-        copy = Collection2(document=doc2)
-        copy['collection']['label'] = name
-
-        doc2.update_data({'collection': copy['collection']})
-        doc2.save()
+      for doc2 in self.get_shared_search_collections():
+        if doc2.id in collection_ids:
+          name = doc2.name + '-copy'
+          copy_doc = doc2.doc.get().copy(name=name, owner=self.user)
+
+          doc2.pk = None
+          doc2.id = None
+          doc2.uuid = str(uuid.uuid4())
+          doc2.name = name
+          doc2.owner = self.user
+          doc2.save()
+
+          doc2.doc.all().delete()
+          doc2.doc.add(copy_doc)
+          doc2.save()
+
+          copy = Collection2(self.user, document=doc2)
+          copy.data['collection']['label'] = name
+
+          doc2.update_data({'collection': copy.data['collection']})
+          doc2.save()
       result['status'] = 0
     except Exception, e:
+      print e
       LOG.warn('Error copying collection: %s' % e)
       result['message'] = unicode(str(e), "utf8")
 

+ 9 - 6
apps/search/src/search/static/search/js/collections.ko.js

@@ -35,6 +35,8 @@ var Collection = function (coll) {
   self.absoluteUrl = ko.observable(coll.absoluteUrl);
   self.owner = ko.observable(coll.owner);
   self.isOwner = ko.observable(coll.isOwner);
+  self.doc1_id = ko.observable(coll.doc1_id);
+
   self.selected = ko.observable(false);
   self.hovered = ko.observable(false);
 
@@ -91,6 +93,10 @@ var SearchCollectionsModel = function (props) {
     return self.selectedCollections().length >= 1 && self.selectedCollections().length == self.selectedOwnerCollections().length;
   });
 
+  self.oneSelected = ko.computed(function() {
+    return self.selectedCollections().length == 1 && self.selectedCollections().length == self.selectedOwnerCollections().length;
+  });
+
   self.selectedImportableCollections = ko.computed(function () {
     return ko.utils.arrayFilter(self.importableCollections(), function (imp) {
       return imp.selected();
@@ -133,8 +139,7 @@ var SearchCollectionsModel = function (props) {
     if (self.atLeastOneSelected()){
       self.isLoading(true);
       $(document).trigger("deleting");
-      $.post(self.DELETE_URL,
-        {
+      $.post(self.DELETE_URL, {
           collections: ko.mapping.toJSON(self.selectedCollections())
         },
         function (data) {
@@ -148,8 +153,7 @@ var SearchCollectionsModel = function (props) {
   self.copyCollections = function (collections) {
     if (self.atLeastOneSelected()){
       $(document).trigger("copying");
-      $.post(self.COPY_URL,
-        {
+      $.post(self.COPY_URL, {
           collections: ko.mapping.toJSON(self.selectedCollections())
         }, function (data) {
           self.updateCollections();
@@ -199,8 +203,7 @@ var SearchCollectionsModel = function (props) {
         name: imp.name()
       });
     });
-    $.post(self.IMPORT_URL,
-      {
+    $.post(self.IMPORT_URL, {
         selected: ko.toJSON(selected)
       },
       function (data) {

+ 35 - 5
apps/search/src/search/templates/admin_collections.mako

@@ -15,7 +15,7 @@
 ## limitations under the License.
 
 <%!
-  from desktop.views import commonheader, commonfooter
+  from desktop.views import commonheader, commonfooter, commonshare
   from django.utils.translation import ugettext as _
 %>
 
@@ -26,6 +26,9 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
 
 <link rel="stylesheet" href="${ static('search/css/admin.css') }">
 
+
+<div id="editor">
+
 <div class="search-bar" style="height: 30px">
   <div class="pull-right">
     % if user.has_hue_permission(action="access", app='indexer'):
@@ -45,12 +48,23 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
       </%def>
 
       <%def name="actions()">
-        <a data-bind="visible: collections().length > 0 && !isLoading(), click: $root.copyCollections, clickBubble: false, css: {'btn': true, 'disabled': ! atLeastOneSelected()}"><i class="fa fa-files-o"></i> ${_('Copy')}</a>
-        <a data-bind="visible: collections().length > 0 && !isLoading(), click: $root.markManyForDeletion, clickBubble: false, css: {'btn': true, 'disabled': ! atLeastOneSelected()}"><i class="fa fa-times"></i> ${_('Delete')}</a>
+        <a data-bind="visible: collections().length > 0 && !isLoading(), click: $root.copyCollections, clickBubble: false, css: {'btn': true, 'disabled': ! atLeastOneSelected()}">
+          <i class="fa fa-files-o"></i> ${_('Copy')}
+        </a>
+        <a data-bind="visible: collections().length > 0 && !isLoading(), click: $root.markManyForDeletion, clickBubble: false, css: {'btn': true, 'disabled': ! atLeastOneSelected()}">
+          <i class="fa fa-times"></i> ${_('Delete')}
+        </a>
+        <a class="share-link btn" rel="tooltip" data-placement="bottom" data-bind="click: function(e){ $root.oneSelected() ? prepareShareModal(e) : void(0) },
+          attr: {'data-original-title': '${ _("Share") } ' + name},
+          css: {'disabled': ! $root.oneSelected(), 'btn': true}">
+          <i class="fa fa-users"></i> ${ _('Share') }
+        </a>
       </%def>
 
       <%def name="creation()">
-        <a data-bind="visible: collections().length > 0 && !isLoading()" class="btn" href="${ url('search:new_search') }" title="${ _('Create a new dashboard') }"><i class="fa fa-plus-circle"></i> ${ _('Create') }</a>
+        <a data-bind="visible: collections().length > 0 && !isLoading()" class="btn" href="${ url('search:new_search') }" title="${ _('Create a new dashboard') }">
+          <i class="fa fa-plus-circle"></i> ${ _('Create') }
+        </a>
       </%def>
     </%actionbar:render>
 
@@ -122,9 +136,18 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
   </div>
 </div>
 
+
+</div>
+
+
+${ commonshare() | n,unicode }
+
+
 <script src="${ static('desktop/ext/js/knockout-min.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/ext/js/knockout.mapping-2.3.2.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('search/js/collections.ko.js') }" type="text/javascript" charset="utf-8"></script>
+<script src="${ static('desktop/js/share.vm.js') }"></script>
+
 
 <script>
   var appProperties = {
@@ -136,8 +159,10 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
   }
 
   var viewModel = new SearchCollectionsModel(appProperties);
+  ko.applyBindings(viewModel, $("#editor")[0]);
 
-  ko.applyBindings(viewModel);
+  shareViewModel = initSharing("#documentShareModal");
+  shareViewModel.setDocId(-1);
 
   $(document).ready(function () {
     viewModel.updateCollections();
@@ -182,6 +207,11 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
     $(document).on("confirmDelete", function () {
       $("#deleteModal").modal('show');
     });
+
+    prepareShareModal = function() {
+      shareViewModel.setDocId(viewModel.selectedCollections()[0].doc1_id());
+      openShareModal();
+    };
   });
 </script>
 

+ 3 - 10
apps/search/src/search/templates/search.mako

@@ -52,7 +52,7 @@ ${ commonheader(_('Search'), "search", user, "80px") | n,unicode }
     </a>
     <span data-bind="visible: columns().length != 0">&nbsp;&nbsp;</span>
 
-    <a class="btn pointer" title="${ _('Share') }" rel="tooltip" data-placement="bottom" data-bind="click: showShareModal, css: {'btn': true}, visible: columns().length != 0, enable: $root.collection.id() != null">
+    <a class="btn pointer" title="${ _('Share search definition') }" rel="tooltip" data-placement="bottom" data-bind="click: showShareModal, css: {'btn': true}, visible: columns().length != 0, enable: $root.collection.id() != null">
       <i class="fa fa-link"></i>
     </a>
 
@@ -1530,10 +1530,10 @@ ${ dashboard.layout_skeleton() }
 <div id="shareModal" class="modal hide" data-backdrop="true">
   <div class="modal-header">
     <a href="javascript: void(0)" data-dismiss="modal" class="pull-right"><i class="fa fa-times"></i></a>
-    <h3>${_('Share this dashboard')}</h3>
+    <h3>${_('Share this dashboard definition')}</h3>
   </div>
   <div class="modal-body">
-    <p>${_('The following URL will show the current dashboard and the applied filters.')}</p>
+    <p>${_('The following URL will show the current dashboard and its applied filters.')}</p>
     <input type="text" style="width: 540px" />
   </div>
   <div class="modal-footer">
@@ -1586,13 +1586,6 @@ ${ dashboard.layout_skeleton() }
                 <input id="settingsdescription" type="text" class="input-xlarge" data-bind="value: $root.collection.description" style="margin-bottom: 0" />
               </div>
             </div>
-            <div class="control-group">
-              <div class="controls">
-                <label class="checkbox">
-                  <input type="checkbox" data-bind="checked: $root.collection.enabled" /> ${ _('Dashboard visible to everybody') }
-                </label>
-              </div>
-            </div>
           </fieldset>
         </form>
       </div>

+ 6 - 10
apps/search/src/search/views.py

@@ -42,6 +42,7 @@ from search.search_controller import SearchController
 LOG = logging.getLogger(__name__)
 
 
+
 def index(request):
   hue_collections = SearchController(request.user).get_search_collections()
   collection_id = request.GET.get('collection')
@@ -50,7 +51,8 @@ def index(request):
     return admin_collections(request, True)
 
   try:
-    collection_doc = hue_collections.get(id=collection_id)
+    collection_doc = Document2.objects.get(id=collection_id)
+    collection_doc.doc.get().can_read_or_exception(request.user)
     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."))
@@ -61,7 +63,7 @@ def index(request):
     'collection': collection,
     'query': query,
     'initial': json.dumps({'collections': [], 'layout': [], 'is_latest': LATEST.get()}),
-    'is_owner': request.user == collection_doc.owner
+    'is_owner': collection_doc.doc.get().can_write(request.user)
   })
 
 
@@ -242,14 +244,8 @@ def admin_collections(request, is_redirect=False):
   if request.GET.get('format') == 'json':
     collections = []
     for collection in existing_hue_collections:
-      massaged_collection = {
-        'id': collection.id,
-        'name': collection.name,
-        'description': collection.description,
-        'absoluteUrl': collection.get_absolute_url(),
-        'owner': collection.owner and collection.owner.username,
-        'isOwner': collection.owner == request.user or request.user.is_superuser
-      }
+      massaged_collection = collection.to_dict()
+      massaged_collection['isOwner'] = collection.doc.get().can_write(request.user)
       collections.append(massaged_collection)
     return JsonResponse(collections, safe=False)
 

+ 4 - 3
desktop/core/src/desktop/models.py

@@ -474,13 +474,13 @@ class Document(models.Model):
     if self.can_read(user):
       return True
     else:
-      raise exception_class(_('Only superusers and %s are allowed to read this document.') % user)
+      raise exception_class(_("Document does not exist or you don't have the permission to access it."))
 
   def can_write_or_exception(self, user, exception_class=PopupException):
     if self.can_write(user):
       return True
     else:
-      raise exception_class(_('Only superusers and %s are allowed to write this document.') % user)
+      raise exception_class(_("Document does not exist or you don't have the permission to access it."))
 
   def copy(self, name=None, owner=None):
     copy_doc = self
@@ -744,7 +744,8 @@ class Document2(models.Model):
       'type': self.type,
       'last_modified': self.last_modified.strftime(UTC_TIME_FORMAT),
       'last_modified_ts': calendar.timegm(self.last_modified.utctimetuple()),
-      'isSelected': False
+      'isSelected': False,
+      'absoluteUrl': self.get_absolute_url()
     }
 
   def can_read_or_exception(self, user):