浏览代码

HUE-2026 [core] Create a doc model write permission

Add permission 'modify'.
Implicit read permission with 'modify'.
Modify pig and oozie apps to use 'can_modify' when
allowing other users to write.
Abraham Elmahrek 11 年之前
父节点
当前提交
28189e9

+ 1 - 1
apps/beeswax/src/beeswax/views.py

@@ -644,7 +644,7 @@ def authorized_get_design(request, design_id, owner_only=False, must_exist=False
       return None
 
   if owner_only:
-    design.doc.get().can_write_or_exception(request.user)
+    design.doc.get().can_modify_or_exception(request.user)
   else:
     design.doc.get().can_read_or_exception(request.user)
 

+ 1 - 1
apps/oozie/src/oozie/models.py

@@ -224,7 +224,7 @@ class Job(models.Model):
 
   def is_editable(self, user):
     """Only owners or admins can modify a job."""
-    return user.is_superuser or self.owner == user
+    return user.is_superuser or self.owner == user or self.doc.get().can_modify(user)
 
   @property
   def data_dict(self):

+ 2 - 2
apps/pig/src/pig/models.py

@@ -87,7 +87,7 @@ class PigScript(Document):
 def create_or_update_script(id, name, script, user, parameters, resources, hadoopProperties, is_design=True):
   try:
     pig_script = PigScript.objects.get(id=id)
-    pig_script.doc.get().can_write_or_exception(user)
+    pig_script.doc.get().can_modify_or_exception(user)
   except PigScript.DoesNotExist:
     pig_script = PigScript.objects.create(owner=user, is_design=is_design)
     Doc.objects.link(pig_script, owner=pig_script.owner, name=name)
@@ -122,7 +122,7 @@ def get_scripts(user, is_design=None):
       'resources': data['resources'],
       'hadoopProperties': data.get('hadoopProperties', []),
       'isDesign': script.is_design,
-      'can_write': script.doc.get().can_write(user)
+      'can_modify': script.doc.get().can_modify(user)
     }
     scripts.append(massaged_script)
 

+ 1 - 1
apps/pig/src/pig/templates/app.mako

@@ -118,7 +118,7 @@ ${ commonheader(None, "pig", user) | n,unicode }
             <li data-bind="click: editScriptProperties" data-section="properties">
               <a href="#"><i class="fa fa-bars"></i> ${ _('Properties') }</a>
             </li>
-            <li data-bind="click: saveScript, visible: currentScript().can_write()">
+            <li data-bind="click: saveScript, visible: currentScript().can_modify()">
               <a href="#" title="${ _('Save the script') }" rel="tooltip" data-placement="right">
                 <i class="fa fa-floppy-o"></i> ${ _('Save') }
               </a>

+ 2 - 2
apps/pig/static/js/pig.ko.js

@@ -37,7 +37,7 @@ var PigScript = function (pigScript) {
   self.id = ko.observable(pigScript.id);
   self.isDesign = ko.observable(pigScript.isDesign);
   self.name = ko.observable(pigScript.name);
-  self.can_write = ko.observable(pigScript.can_write);
+  self.can_modify = ko.observable(pigScript.can_modify);
   self.script = ko.observable(pigScript.script);
   self.scriptSumup = ko.observable(pigScript.script.replace(/\W+/g, ' ').substring(0, 100));
   self.isRunning = ko.observable(false);
@@ -207,7 +207,7 @@ var PigViewModel = function (props) {
     resources: self.LABELS.NEW_SCRIPT_RESOURCES,
     hadoopProperties: self.LABELS.NEW_SCRIPT_HADOOP_PROPERTIES,
     parentModel: self,
-    can_write: true
+    can_modify: true
   };
 
   self.currentScript = ko.observable(new PigScript(_defaultScript));

+ 17 - 6
desktop/core/src/desktop/api.py

@@ -128,7 +128,8 @@ def massaged_documents_for_json(documents, user):
   docs = {}
 
   for document in documents:
-    perms = document.list_permissions()
+    read_perms = document.list_permissions(perm='read')
+    modify_perms = document.list_permissions(perm='modify')
     docs[document.id] = {
       'id': document.id,
       'contentType': document.content_type.name,
@@ -139,8 +140,12 @@ def massaged_documents_for_json(documents, user):
       'tags': [{'id': tag.id, 'name': tag.tag} for tag in document.tags.all()],
       'perms': {
         'read': {
-          'users': [{'id': friends.id, 'username': friends.username} for friends in perms.users.all()],
-          'groups': [{'id': group.id, 'name': group.name} for group in perms.groups.all()]
+          'users': [{'id': perm_user.id, 'username': perm_user.username} for perm_user in read_perms.users.all()],
+          'groups': [{'id': perm_group.id, 'name': perm_group.name} for perm_group in read_perms.groups.all()]
+        },
+        'modify': {
+          'users': [{'id': perm_user.id, 'username': perm_user.username} for perm_user in modify_perms.users.all()],
+          'groups': [{'id': perm_group.id, 'name': perm_group.name} for perm_group in modify_perms.groups.all()]
         }
       },
       'owner': document.owner.username,
@@ -153,7 +158,8 @@ def massaged_documents_for_json(documents, user):
 
 
 def massage_doc_for_json(doc, user):
-  perms = doc.list_permissions()
+  read_perms = doc.list_permissions(perm='read')
+  modify_perms = doc.list_permissions(perm='modify')
   return {
       'id': doc.id,
       'contentType': doc.content_type.name,
@@ -164,8 +170,12 @@ def massage_doc_for_json(doc, user):
       'tags': [{'id': tag.id, 'name': tag.tag} for tag in doc.tags.all()],
       'perms': {
         'read': {
-          'users': [{'id': perm_user.id, 'username': perm_user.username} for perm_user in perms.users.all()],
-          'groups': [{'id': perm_group.id, 'name': perm_group.name} for perm_group in perms.groups.all()]
+          'users': [{'id': perm_user.id, 'username': perm_user.username} for perm_user in read_perms.users.all()],
+          'groups': [{'id': perm_group.id, 'name': perm_group.name} for perm_group in read_perms.groups.all()]
+        },
+        'modify': {
+          'users': [{'id': perm_user.id, 'username': perm_user.username} for perm_user in modify_perms.users.all()],
+          'groups': [{'id': perm_group.id, 'name': perm_group.name} for perm_group in modify_perms.groups.all()]
         }
       },
       'owner': doc.owner.username,
@@ -257,6 +267,7 @@ def update_permissions(request):
       response['status'] = 0
       response['doc'] = massage_doc_for_json(doc, request.user)
     except Exception, e:
+      LOG.exception(e.message)
       response['message'] = force_unicode(e)
   else:
     response['message'] = _('POST request only')

+ 166 - 6
desktop/core/src/desktop/api_tests.py

@@ -199,8 +199,20 @@ class TestDocModelPermissions():
 
     # Share by user
     response = self.client.post("/desktop/api/doc/update_permissions", {
-        'doc_id': doc.id,
-        'data': json.dumps({'read': {'user_ids': [self.user.id, self.user_not_me.id], 'group_ids': []}})
+      'doc_id': doc.id,
+      'data': json.dumps({
+        'read': {
+          'user_ids': [
+            self.user.id,
+            self.user_not_me.id
+          ],
+          'group_ids': []
+        },
+        'modify': {
+          'user_ids': [],
+          'group_ids': []
+        }
+      })
     })
 
     assert_equal(0, json.loads(response.content)['status'], response.content)
@@ -212,8 +224,19 @@ class TestDocModelPermissions():
 
     # Un-share
     response = self.client.post("/desktop/api/doc/update_permissions", {
-        'doc_id': doc.id,
-        'data': json.dumps({'read': {'user_ids': [self.user.id], 'group_ids': []}})
+      'doc_id': doc.id,
+      'data': json.dumps({
+        'read': {
+          'user_ids': [
+            self.user.id
+          ],
+          'group_ids': []
+        },
+        'modify': {
+          'user_ids': [],
+          'group_ids': []
+        }
+      })
     })
 
     assert_equal(0, json.loads(response.content)['status'], response.content)
@@ -227,8 +250,21 @@ class TestDocModelPermissions():
     default_group = get_default_user_group()
 
     response = self.client.post("/desktop/api/doc/update_permissions", {
-        'doc_id': doc.id,
-        'data': json.dumps({'read': {'user_ids': [self.user.id], 'group_ids': [default_group.id]}})
+      'doc_id': doc.id,
+      'data': json.dumps({
+        'read': {
+          'user_ids': [
+            self.user.id
+          ],
+          'group_ids': [
+            default_group.id
+          ]
+        },
+        'modify': {
+          'user_ids': [],
+          'group_ids': []
+        }
+      })
     })
 
     assert_equal(0, json.loads(response.content)['status'], response.content)
@@ -237,3 +273,127 @@ class TestDocModelPermissions():
     assert_true(doc_id in json.loads(response.context['json_documents']))
     response = self.client_not_me.get('/home')
     assert_true(doc_id in json.loads(response.context['json_documents']))
+
+    # Un-share
+    response = self.client.post("/desktop/api/doc/update_permissions", {
+      'doc_id': doc.id,
+      'data': json.dumps({
+        'read': {
+          'user_ids': [
+            self.user.id
+          ],
+          'group_ids': []
+        },
+        'modify': {
+          'user_ids': [],
+          'group_ids': []
+        }
+      })
+    })
+
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    response = self.client.get('/home')
+    assert_true(doc_id in json.loads(response.context['json_documents']))
+    response = self.client_not_me.get('/home')
+    assert_false(doc_id in json.loads(response.context['json_documents']))
+
+    # Modify by user
+    response = self.client.post("/desktop/api/doc/update_permissions", {
+      'doc_id': doc.id,
+      'data': json.dumps({
+        'read': {
+          'user_ids': [
+            self.user.id
+          ],
+          'group_ids': []
+        },
+        'modify': {
+          'user_ids': [
+            self.user_not_me.id
+          ],
+          'group_ids': []
+        }
+      })
+    })
+
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    response = self.client.get('/home')
+    assert_true(doc_id in json.loads(response.context['json_documents']))
+    response = self.client_not_me.get('/home')
+    assert_true(doc_id in json.loads(response.context['json_documents']))
+
+    # Un-share
+    response = self.client.post("/desktop/api/doc/update_permissions", {
+      'doc_id': doc.id,
+      'data': json.dumps({
+        'read': {
+          'user_ids': [
+            self.user.id
+          ],
+          'group_ids': []
+        },
+        'modify': {
+          'user_ids': [],
+          'group_ids': []
+        }
+      })
+    })
+
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    response = self.client.get('/home')
+    assert_true(doc_id in json.loads(response.context['json_documents']))
+    response = self.client_not_me.get('/home')
+    assert_false(doc_id in json.loads(response.context['json_documents']))
+
+    # Modify by group
+    response = self.client.post("/desktop/api/doc/update_permissions", {
+      'doc_id': doc.id,
+      'data': json.dumps({
+        'read': {
+          'user_ids': [
+            self.user.id
+          ],
+          'group_ids': []
+        },
+        'modify': {
+          'user_ids': [],
+          'group_ids': [
+            default_group.id
+          ]
+        }
+      })
+    })
+
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    response = self.client.get('/home')
+    assert_true(doc_id in json.loads(response.context['json_documents']))
+    response = self.client_not_me.get('/home')
+    assert_true(doc_id in json.loads(response.context['json_documents']))
+
+    # Un-share
+    response = self.client.post("/desktop/api/doc/update_permissions", {
+      'doc_id': doc.id,
+      'data': json.dumps({
+        'read': {
+          'user_ids': [
+            self.user.id
+          ],
+          'group_ids': []
+        },
+        'modify': {
+          'user_ids': [],
+          'group_ids': []
+        }
+      })
+    })
+
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    response = self.client.get('/home')
+    assert_true(doc_id in json.loads(response.context['json_documents']))
+    response = self.client_not_me.get('/home')
+    assert_false(doc_id in json.loads(response.context['json_documents']))

+ 44 - 23
desktop/core/src/desktop/models.py

@@ -165,7 +165,6 @@ class DocumentTag(models.Model):
 class DocumentManager(models.Manager):
 
   def documents(self, user):
-    # Check for READ perm only, not write
     return Document.objects.filter(Q(owner=user) | Q(documentpermission__users=user) | Q(documentpermission__groups__in=user.groups.all())).distinct()
 
   def get_docs(self, user, model_class=None, extra=None):
@@ -361,11 +360,11 @@ class Document(models.Model):
 
   def is_editable(self, user):
     """Deprecated by can_read"""
-    return self.can_write(user)
+    return self.can_modify(user)
 
   def can_edit_or_exception(self, user, exception_class=PopupException):
-    """Deprecated by can_write_or_exception"""
-    return self.can_write_or_exception(user, exception_class)
+    """Deprecated by can_modify_or_exception"""
+    return self.can_modify_or_exception(user, exception_class)
 
   def add_tag(self, tag):
     self.tags.add(tag)
@@ -393,14 +392,14 @@ class Document(models.Model):
     #default_tag = DocumentTag.objects.get_default_tag(user=self.owner)
     #self.tags.remove(default_tag)
 
-  def share_to_default(self):
-    DocumentPermission.objects.share_to_default(self)
+  def share_to_default(self, name='read'):
+    DocumentPermission.objects.share_to_default(self, name=name)
 
   def can_read(self, user):
     return user.is_superuser or self.owner == user or Document.objects.get_docs(user).filter(id=self.id).exists()
 
-  def can_write(self, user):
-    return user.is_superuser or self.owner == user
+  def can_modify(self, user):
+    return user.is_superuser or self.owner == user or user in self.list_permissions('modify').users.all()
 
   def can_read_or_exception(self, user, exception_class=PopupException):
     if self.can_read(user):
@@ -408,12 +407,16 @@ class Document(models.Model):
     else:
       raise exception_class(_('Only superusers and %s are allowed to read this document.') % user)
 
-  def can_write_or_exception(self, user, exception_class=PopupException):
-    if self.can_write(user):
+  def can_modify_or_exception(self, user, exception_class=PopupException):
+    if self.can_modify(user):
       return True
     else:
       raise exception_class(_('Only superusers and %s are allowed to modify this document.') % user)
 
+  def can_write_or_exception(self, user, exception_class=PopupException):
+    """ Deprecated by can_modify_or_exception """
+    return self.can_modify_or_exception(user, exception_class)
+
   def copy(self, name=None, owner=None):
     copy_doc = self
 
@@ -480,26 +483,40 @@ class Document(models.Model):
 
       if perm.get('group_ids'):
         groups = auth_models.Group.objects.in_bulk(perm.get('group_ids'))
+      else:
+        groups = []
 
       DocumentPermission.objects.sync(document=self, name=name, users=users, groups=groups)
 
-  def list_permissions(self):
-    return DocumentPermission.objects.list(document=self)
+  def list_permissions(self, perm='read'):
+    return DocumentPermission.objects.list(document=self, perm=perm)
 
 
 class DocumentPermissionManager(models.Manager):
 
-  def share_to_default(self, document):
+  def _check_perm(self, name):
+    perms = (DocumentPermission.READ_PERM, DocumentPermission.MODIFY_PERM)
+    if name not in perms:
+      perms_string = ' and '.join(', '.join(perms).rsplit(', ', 1))
+      raise PopupException(_('Only %s permissions are supported, not %s.') % (perms_string, name))
+
+
+  def share_to_default(self, document, name='read'):
     from useradmin.models import get_default_user_group # Remove build dependency
-    perm, created = DocumentPermission.objects.get_or_create(doc=document)
+    
+    self._check_perm(name)
+
+    if name == DocumentPermission.MODIFY_PERM:
+      perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=DocumentPermission.MODIFY_PERM)
+    else:
+      perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=DocumentPermission.READ_PERM)
     default_group = get_default_user_group()
 
     if default_group:
       perm.groups.add(default_group)
 
   def update(self, document, name='read', users=None, groups=None, add=True):
-    if name != DocumentPermission.READ_PERM:
-      raise PopupException(_('Only %s permissions is supported, not %s.') % (DocumentPermission.READ_PERM, name))
+    self._check_perm(name)
 
     perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=name)
 
@@ -519,8 +536,7 @@ class DocumentPermissionManager(models.Manager):
       perm.delete()
 
   def sync(self, document, name='read', users=None, groups=None):
-    if name != DocumentPermission.READ_PERM:
-      raise PopupException(_('Only %s permissions is supported, not %s.') % (DocumentPermission.READ_PERM, name))
+    self._check_perm(name)
 
     perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=name)
 
@@ -537,12 +553,12 @@ class DocumentPermissionManager(models.Manager):
     if not users and not groups:
       perm.delete()
 
-  def list(self, document):
+  def list(self, document, perm='read'):
     try:
-      perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=DocumentPermission.READ_PERM)
-    except DocumentPermission.MultipleObjectsReturned, ex:
+      perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=perm)
+    except DocumentPermission.MultipleObjectsReturned:
       # We can delete duplicate perms of a document
-      dups = DocumentPermission.objects.filter(doc=document, perms=DocumentPermission.READ_PERM)
+      dups = DocumentPermission.objects.filter(doc=document, perms=perm)
       perm = dups[0]
       for dup in dups[1:]:
         LOG.warn('Deleting duplicate %s' % dup)
@@ -552,12 +568,17 @@ class DocumentPermissionManager(models.Manager):
 
 class DocumentPermission(models.Model):
   READ_PERM = 'read'
+  MODIFY_PERM = 'modify'
 
   doc = models.ForeignKey(Document)
 
   users = models.ManyToManyField(auth_models.User, db_index=True)
   groups = models.ManyToManyField(auth_models.Group, db_index=True)
-  perms = models.TextField(default=READ_PERM, choices=((READ_PERM, 'read'),))
+  # @TODO(Abe): Rename to "perm"
+  perms = models.TextField(default=READ_PERM, choices=(
+    (READ_PERM, 'read'),
+    (MODIFY_PERM, 'modify'),
+  ))
 
 
   objects = DocumentPermissionManager()

+ 67 - 19
desktop/core/src/desktop/templates/home.mako

@@ -84,6 +84,10 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
     color: #338bb8!important;
   }
 
+  #documentShareModal .modal-body {
+    overflow-y: initial;
+  }
+
 </style>
 
 <div class="navbar navbar-inverse navbar-fixed-top nokids">
@@ -330,26 +334,34 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
   <div class="modal-body">
     <p>
       <div class="row-fluid">
-        <div class="span6">
-          <h4 class="muted" style="margin-top:0px">${_('Users')}</h4>
-          <div data-bind="visible: selectedDoc().perms.read.users.length == 0">${_('The document is not shared with any user.')}</div>
-          <ul class="unstyled airy" data-bind="foreach: selectedDoc().perms.read.users">
-            <li><span class="badge badge-left"><i class="fa fa-user"></i> <span data-bind="text: prettifyUsername(id)"></span></span><span class="badge badge-important badge-right trash-share" data-bind="click: removeUserShare"><i class="fa fa-times-circle"></i></span></li>
-          </ul>
-        </div>
-        <div class="span6">
-          <h4 class="muted" style="margin-top:0px">${_('Groups')}</h4>
-          <div data-bind="visible: selectedDoc().perms.read.groups.length == 0">${_('The document is not shared with any group.')}</div>
-          <ul class="unstyled airy" data-bind="foreach: selectedDoc().perms.read.groups">
-            <li><span class="badge badge-left"><i class="fa fa-users"></i> <span data-bind="text: name"></span></span><span class="badge badge-important badge-right trash-share" data-bind="click: removeGroupShare"><i class="fa fa-times-circle"></i></span></li>
-          </ul>
-        </div>
+        <div data-bind="visible: permsEmpty">${_('The document is not shared with any one.')}</div>
+        <ul class="unstyled airy" data-bind="foreach: selectedDoc().perms.read.groups">
+          <li><i class="fa fa-users"></i> ${ _('Users from') } <span data-bind="text: name"></span> ${ _('may access this document') }</span><span class="trash-share" data-bind="click: removeGroupReadShare"> <i class="fa fa-times"></i></li>
+        </ul>
+        <ul class="unstyled airy" data-bind="foreach: selectedDoc().perms.modify.groups">
+          <li><i class="fa fa-users"></i> ${ _('Users from') } <span data-bind="text: name"></span> ${ _('may modify this document') }</span><span class="trash-share" data-bind="click: removeGroupModifyShare"> <i class="fa fa-times"></i></li>
+        </ul>
+        <ul class="unstyled airy" data-bind="foreach: selectedDoc().perms.read.users">
+          <li><i class="fa fa-user"></i> <span data-bind="text: prettifyUsername(id)"> ${ _('may access this document') }</span></span><span class="trash-share" data-bind="click: removeUserReadShare"> <i class="fa fa-times"></i></li>
+        </ul>
+        <ul class="unstyled airy" data-bind="foreach: selectedDoc().perms.modify.users">
+          <li><i class="fa fa-user"></i> <span data-bind="text: prettifyUsername(id)"></span> ${ _('may modify this document') }</span><span class="trash-share" data-bind="click: removeUserModifyShare"> <i class="fa fa-times"></i></li>
+        </ul>
       </div>
       <div class="clearfix"></div>
       <div style="margin-top: 20px">
         <div class="input-append">
           <input id="documentShareTypeahead" type="text" style="width: 460px" placeholder="${_('You can type a username or a group')}">
-          <a id="documentShareAddBtn" class="btn" type="button"><i class="fa fa-plus-circle"></i> ${_('Add')}</a>
+          <div class="btn-group">
+            <a id="documentShareAddBtn" class="btn"><i class="fa fa-plus-circle"></i> <span data-bind="text: selectedPermLabel"></span></a>
+            <a class="btn dropdown-toggle" data-toggle="dropdown">
+              <span class="caret"></span>
+            </a>
+            <ul class="dropdown-menu">
+              <li><a data-bind="click: changeDocumentSharePerm.bind(null, 'read')" href="javascript:void(0)">${ _('Read') }</a></li>
+              <li><a data-bind="click: changeDocumentSharePerm.bind(null, 'modify')" href="javascript:void(0)">${ _('Modify') }</a></li>
+            </ul>
+          </div>
         </div>
       </div>
     </p>
@@ -483,10 +495,10 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
     function handleTypeaheadSelection() {
       if (selectedUserOrGroup != null) {
         if (selectedUserOrGroup.hasOwnProperty("username")) {
-          viewModel.selectedDoc().perms.read.users.push(selectedUserOrGroup);
+          viewModel.selectedDoc().perms[viewModel.selectedPerm()].users.push(selectedUserOrGroup);
         }
         else {
-          viewModel.selectedDoc().perms.read.groups.push(selectedUserOrGroup);
+          viewModel.selectedDoc().perms[viewModel.selectedPerm()].groups.push(selectedUserOrGroup);
         }
         viewModel.selectedDoc.valueHasMutated();
         shareDocFinal();
@@ -557,7 +569,7 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
     $("#documentShareModal").modal("show");
   }
 
-  function removeUserShare(user) {
+  function removeUserReadShare(user) {
     $(viewModel.selectedDoc().perms.read.users).each(function (cnt, item) {
       if (item.id == user.id) {
         viewModel.selectedDoc().perms.read.users.splice(cnt, 1);
@@ -567,7 +579,17 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
     shareDocFinal();
   }
 
-  function removeGroupShare(group) {
+  function removeUserModifyShare(user) {
+    $(viewModel.selectedDoc().perms.modify.users).each(function (cnt, item) {
+      if (item.id == user.id) {
+        viewModel.selectedDoc().perms.modify.users.splice(cnt, 1);
+      }
+    });
+    viewModel.selectedDoc.valueHasMutated();
+    shareDocFinal();
+  }
+
+  function removeGroupReadShare(group) {
     $(viewModel.selectedDoc().perms.read.groups).each(function (cnt, item) {
       if (item.id == group.id) {
         viewModel.selectedDoc().perms.read.groups.splice(cnt, 1);
@@ -577,11 +599,29 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
     shareDocFinal();
   }
 
+  function removeGroupModifyShare(group) {
+    $(viewModel.selectedDoc().perms.modify.groups).each(function (cnt, item) {
+      if (item.id == group.id) {
+        viewModel.selectedDoc().perms.modify.groups.splice(cnt, 1);
+      }
+    });
+    viewModel.selectedDoc.valueHasMutated();
+    shareDocFinal();
+  }
+
+  function changeDocumentSharePerm(perm) {
+    viewModel.selectedPerm(perm);
+  }
+
   function shareDocFinal() {
     var _postPerms = {
       read: {
         user_ids: [],
         group_ids: []
+      },
+      modify: {
+        user_ids: [],
+        group_ids: []
       }
     }
 
@@ -593,6 +633,14 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
       _postPerms.read.group_ids.push(item.id);
     });
 
+    $(viewModel.selectedDoc().perms.modify.users).each(function (cnt, item) {
+      _postPerms.modify.user_ids.push(item.id);
+    });
+
+    $(viewModel.selectedDoc().perms.modify.groups).each(function (cnt, item) {
+      _postPerms.modify.group_ids.push(item.id);
+    });
+
     $.post("/desktop/api/doc/update_permissions", {
       doc_id: viewModel.selectedDoc().id,
       data: JSON.stringify(_postPerms)

+ 18 - 0
desktop/core/static/js/home.vm.js

@@ -29,9 +29,27 @@ function HomeViewModel(json_tags, json_docs) {
       read: {
         users: [],
         groups: []
+      },
+      modify: {
+        users: [],
+        groups: []
       }
     }
   }));
+  self.selectedPerm = ko.observable('read');
+  self.selectedPermLabel = ko.computed(function() {
+    return self.selectedPerm().replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
+  });
+  self.permsEmpty = ko.computed(function() {
+    for (var perm in self.selectedDoc().perms) {
+      if ($.isFunction(self.selectedDoc().perms[perm].users)) {
+        return !!self.selectedDoc().perms[perm].users();
+      } else{
+        return !!self.selectedDoc().perms[perm].users;
+      }
+    }
+    return true;
+  });
 
   self.selectedTag = ko.observable({});
   self.selectedTagForDelete = ko.observable({