Browse Source

[core] Doc model API and desktop permissions cleanup

Looks big but mostly URL rewrite
Move API to /desktop/api to unify and simplify permissions (as desktop
should always be non restricted, except at the view level. currently
non admin user can't log out :)
Improved testing of DocModel
Improved testing by switching to non admin user some tests
Add autocomplete to DESCRIBE statement in Beeswax
Romain Rigaux 12 năm trước cách đây
mục cha
commit
100e22af79

+ 1 - 1
apps/beeswax/src/beeswax/templates/execute.mako

@@ -1045,7 +1045,7 @@ $(document).ready(function () {
         CodeMirror.possibleTable = false;
         CodeMirror.tableFieldMagic = false;
         if (_before.toUpperCase().indexOf(" FROM ") > -1 && _before.toUpperCase().indexOf(" ON ") == -1 && _before.toUpperCase().indexOf(" WHERE ") == -1 ||
-            _before.toUpperCase().indexOf("REFRESH") > -1 || _before.toUpperCase().indexOf("METADATA") > -1) {
+            _before.toUpperCase().indexOf("REFRESH") > -1 || _before.toUpperCase().indexOf("METADATA") > -1 || _before.toUpperCase().indexOf("DESCRIBE") > -1) {
           CodeMirror.possibleTable = true;
         }
         CodeMirror.possibleSoloField = false;

+ 1 - 1
apps/useradmin/src/useradmin/urls.py

@@ -21,12 +21,12 @@ from desktop.lib.django_util import get_username_re_rule, get_groupname_re_rule
 username_re = get_username_re_rule()
 groupname_re = get_groupname_re_rule()
 
+
 urlpatterns = patterns('useradmin.views',
   url(r'^$', 'list_users'),
   url(r'^users$', 'list_users'),
   url(r'^groups$', 'list_groups'),
   url(r'^permissions$', 'list_permissions'),
-  url(r'^autocomplete', 'list_for_autocomplete'),
   url(r'^users/edit/(?P<username>%s)$' % (username_re,), 'edit_user'),
   url(r'^users/add_ldap_users$', 'add_ldap_users'),
   url(r'^users/add_ldap_groups$', 'add_ldap_groups'),

+ 1 - 4
apps/useradmin/src/useradmin/views.py

@@ -24,10 +24,7 @@ import grp
 import logging
 import threading
 import subprocess
-try:
-  import json
-except ImportError:
-  import simplejson as json
+import json
 
 import ldap
 import ldap_access

+ 171 - 0
desktop/core/src/desktop/api.py

@@ -0,0 +1,171 @@
+#!/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
+import json
+import time
+
+
+from django.http import HttpResponse
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _
+
+from desktop.lib.i18n import force_unicode
+from desktop.models import Document, DocumentTag
+
+
+LOG = logging.getLogger(__name__)
+
+
+def list_docs(request):
+  docs = Document.objects.get_docs(request.user).order_by('-last_modified')[:1000]
+  return HttpResponse(json.dumps(massaged_documents_for_json(docs)), mimetype="application/json")
+
+
+def list_tags(request):
+  tags = DocumentTag.objects.get_tags(user=request.user)
+  return HttpResponse(json.dumps(massaged_tags_for_json(tags, request.user)), mimetype="application/json")
+
+
+def massaged_documents_for_json(documents):
+  return [massage_doc_for_json(doc) for doc in documents]
+
+
+def massage_doc_for_json(doc):
+  perms = doc.list_permissions()
+  return {
+      'id': doc.id,
+      'contentType': doc.content_type.name,
+      'icon': doc.icon,
+      'name': doc.name,
+      'url': doc.content_object.get_absolute_url(),
+      'description': doc.description,
+      'tags': [{'id': tag.id, 'name': tag.tag} for tag in doc.tags.all()],
+      'perms': {
+        'read': {
+          'users': [{'id': user.id, 'username': user.username} for user in perms.users.all()],
+          'groups': [{'id': group.id, 'name': group.name} for group in perms.groups.all()]
+        }
+      },
+      'owner': doc.owner.username,
+      'lastModified': doc.last_modified.strftime("%x %X"),
+      'lastModifiedInMillis': time.mktime(doc.last_modified.timetuple())
+    }
+
+def massaged_tags_for_json(tags, user):
+  ts = []
+  trash = DocumentTag.objects.get_trash_tag(user)
+  history = DocumentTag.objects.get_history_tag(user)
+
+  for tag in tags:
+    massaged_tag = {
+      'id': tag.id,
+      'name': tag.tag,
+      'isTrash': tag.id == trash.id,
+      'isHistory': tag.id == history.id,
+      'isExample': tag.tag == DocumentTag.EXAMPLE
+    }
+    ts.append(massaged_tag)
+
+  return ts
+
+def add_tag(request):
+  response = {'status': -1, 'message': ''}
+
+  if request.method == 'POST':
+    try:
+      tag = DocumentTag.objects.create_tag(request.user, request.POST['name'])
+      response['tag_id'] = tag.id
+      response['status'] = 0
+    except Exception, e:
+      response['message'] = force_unicode(e)
+  else:
+    response['message'] = _('POST request only')
+
+  return HttpResponse(json.dumps(response), mimetype="application/json")
+
+
+def tag(request):
+  response = {'status': -1, 'message': ''}
+
+  if request.method == 'POST':
+    request_json = json.loads(request.POST['data'])
+    try:
+      tag = DocumentTag.objects.tag(request.user, request_json['doc_id'], request_json.get('tag'), request_json.get('tag_id'))
+      response['tag_id'] = tag.id
+      response['status'] = 0
+    except Exception, e:
+      response['message'] = force_unicode(e)
+  else:
+    response['message'] = _('POST request only')
+
+  return HttpResponse(json.dumps(response), mimetype="application/json")
+
+
+def update_tags(request):
+  response = {'status': -1, 'message': ''}
+
+  if request.method == 'POST':
+    request_json = json.loads(request.POST['data'])
+    try:
+      doc = DocumentTag.objects.update_tags(request.user, request_json['doc_id'], request_json['tag_ids'])
+      response['doc'] = massage_doc_for_json(doc)
+      response['status'] = 0
+    except Exception, e:
+      response['message'] = force_unicode(e)
+  else:
+    response['message'] = _('POST request only')
+
+  return HttpResponse(json.dumps(response), mimetype="application/json")
+
+
+def remove_tags(request):
+  response = {'status': -1, 'message': _('Error')}
+
+  if request.method == 'POST':
+    request_json = json.loads(request.POST['data'])
+    try:
+      for tag_id in request_json['tag_ids']:
+        DocumentTag.objects.delete_tag(tag_id, request.user)
+      response['message'] = _('Tag(s) removed!')
+      response['status'] = 0
+    except Exception, e:
+      response['message'] = force_unicode(e)
+  else:
+    response['message'] = _('POST request only')
+
+  return HttpResponse(json.dumps(response), mimetype="application/json")
+
+
+def update_permissions(request):
+  response = {'status': -1, 'message': _('Error')}
+
+  if request.method == 'POST':
+    data = json.loads(request.POST['data'])
+    doc_id = request.POST['doc_id']
+    try:
+      doc = Document.objects.get_doc(doc_id, request.user)
+      doc.sync_permissions(data)
+      response['message'] = _('Permissions updated!')
+      response['status'] = 0
+      response['doc'] = massage_doc_for_json(doc)
+    except Exception, e:
+      response['message'] = force_unicode(e)
+  else:
+    response['message'] = _('POST request only')
+
+  return HttpResponse(json.dumps(response), mimetype="application/json")

+ 215 - 0
desktop/core/src/desktop/api_tests.py

@@ -0,0 +1,215 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# 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 json
+
+from nose.tools import assert_true, assert_false, assert_equal, assert_not_equal, assert_raises
+
+from django.conf.urls.defaults import patterns, url
+from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse
+from django.db.models import query, CharField, SmallIntegerField
+
+from desktop.lib.django_test_util import make_logged_in_client
+from desktop.lib.test_utils import grant_access
+from desktop.models import DocumentTag , Document
+from pig.models import PigScript
+from useradmin.models import get_default_user_group
+
+
+class TestDocModelTags():
+
+  def setUp(self):
+    self.client = make_logged_in_client(username="tag_user", recreate=True, is_superuser=False)
+    self.client_not_me = make_logged_in_client(username="not_tag_user", recreate=True, is_superuser=False)
+
+    self.user = User.objects.get(username="tag_user")
+    self.user_not_me = User.objects.get(username="not_tag_user")
+
+    grant_access(self.user.username, self.user.username, "desktop")
+    grant_access(self.user_not_me.username, self.user_not_me.username, "desktop")
+
+  def add_tag(self, name):
+    response = self.client.post("/desktop/api/tag/add_tag", {'name': name})
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+    return json.loads(response.content)['tag_id']
+
+  def add_doc(self, name):
+    script = PigScript.objects.create(owner=self.user)
+    doc = Document.objects.link(script, owner=script.owner, name=name)
+    return script, doc
+
+  def test_add_tag(self):
+    response = self.client.get("/desktop/api/tag/add_tag")
+    assert_equal(-1, json.loads(response.content)['status'])
+
+    tag_id = self.add_tag('my_tag')
+
+    assert_true(DocumentTag.objects.filter(id=tag_id, owner=self.user, tag='my_tag').exists())
+
+  def test_remove_tags(self):
+    response = self.client.post("/desktop/api/tag/add_tag", {'name': 'my_tag'})
+    tag_id = json.loads(response.content)['tag_id']
+
+    response = self.client.get("/desktop/api/tag/remove_tags")
+    assert_equal(-1, json.loads(response.content)['status'])
+
+    response = self.client_not_me.post("/desktop/api/tag/remove_tags", {'data': json.dumps({'tag_ids': [tag_id]})})
+    assert_equal(-1, json.loads(response.content)['status'], response.content)
+
+    response = self.client.post("/desktop/api/tag/remove_tags", {'data': json.dumps({'tag_ids': [tag_id]})})
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    assert_false(DocumentTag.objects.filter(id=tag_id).exists())
+
+  def test_list_tags(self):
+    tag_id = self.add_tag('my_list_tags')
+
+    response = self.client.get("/desktop/api/tag/list_tags")
+    assert_true([tag for tag in json.loads(response.content) if tag['id'] == tag_id], response.content)
+
+  def test_list_docs(self):
+    script, doc = self.add_doc('test-pig')
+
+    response = self.client.get("/desktop/api/doc/list_docs")
+    assert_true([doc for doc in json.loads(response.content) if doc['id'] == script.id], response.content)
+
+  def test_tag(self):
+    script, doc = self.add_doc('tag_pig')
+
+    response = self.client.post("/desktop/api/doc/tag", {'data': json.dumps({'doc_id': doc.id, 'tag': 'pig'})})
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    tag2_id = self.add_tag('pig2')
+
+    response = self.client.post("/desktop/api/doc/tag", {'data': json.dumps({'doc_id': doc.id, 'tag_id': tag2_id})})
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+  def test_update_tags(self):
+    script, doc = self.add_doc('update_tags')
+    default_tag = DocumentTag.objects.get_default_tag(self.user)
+
+    tag1_id = self.add_tag('update_tags_1')
+    tag2_id = self.add_tag('update_tags_2')
+
+    response = self.client.post("/desktop/api/doc/update_tags", {'data': json.dumps({'doc_id': doc.id, 'tag_ids': [tag1_id, tag2_id]})})
+    content = json.loads(response.content)
+
+    assert_equal(0, content['status'])
+    assert_equal([
+        {"id": default_tag.id, "name": "default"},
+        {"id": tag1_id, "name": "update_tags_1"},
+        {"id": tag2_id, "name": "update_tags_2"}
+      ], content['doc']['tags'])
+
+    # No perms
+    response = self.client_not_me.post("/desktop/api/doc/update_tags", {'data': json.dumps({'doc_id': doc.id, 'tag_ids': [tag1_id, tag2_id]})})
+    content = json.loads(response.content)
+
+    assert_equal(-1, content['status'])
+
+    # todo no default tag on test user?
+
+
+
+class TestDocModelPermissions():
+
+  def setUp(self):
+    self.client = make_logged_in_client(username="perm_user", groupname="default", recreate=True, is_superuser=False)
+    self.client_not_me = make_logged_in_client(username="not_perm_user", groupname="default", recreate=True, is_superuser=False)
+
+    self.user = User.objects.get(username="perm_user")
+    self.user_not_me = User.objects.get(username="not_perm_user")
+
+    grant_access(self.user.username, self.user.username, "desktop")
+    grant_access(self.user_not_me.username, self.user_not_me.username, "desktop")
+
+    PigScript.objects.filter(owner=self.user).delete()
+    Document.objects.filter(owner=self.user).delete()
+
+  def _add_doc(self, name):
+    script, created = PigScript.objects.get_or_create(owner=self.user)
+    doc = Document.objects.link(script, owner=script.owner, name=name)
+    return script, doc
+
+  def test_update_permissions(self):
+    script, doc = self._add_doc('test_update_permissions')
+
+    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': []}})
+    })
+
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+  def test_share_document_permissions(self):
+    # No doc
+    response = self.client.get('/home')
+    assert_equal([], list(response.context['documents']))
+    response = self.client_not_me.get('/home')
+    assert_equal([], list(response.context['documents']))
+
+    # Add doc
+    script, doc = self._add_doc('test_update_permissions')
+
+    response = self.client.get('/home')
+    assert_equal([doc], list(response.context['documents']))
+    response = self.client_not_me.get('/home')
+    assert_equal([], list(response.context['documents']))
+
+    # 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': []}})
+    })
+
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    response = self.client.get('/home')
+    assert_equal([doc], list(response.context['documents']))
+    response = self.client_not_me.get('/home')
+    assert_equal([doc], list(response.context['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': []}})
+    })
+
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    response = self.client.get('/home')
+    assert_equal([doc], list(response.context['documents']))
+    response = self.client_not_me.get('/home')
+    assert_equal([], list(response.context['documents']))
+
+    # Share by group
+    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]}})
+    })
+
+    assert_equal(0, json.loads(response.content)['status'], response.content)
+
+    response = self.client.get('/home')
+    assert_equal([doc], list(response.context['documents']))
+    response = self.client_not_me.get('/home')
+    assert_equal([doc], list(response.context['documents']))

+ 3 - 3
desktop/core/src/desktop/lib/django_util_test.py

@@ -162,12 +162,12 @@ def test_reverse_with_get():
   # Basic view
   assert_equal("/", reverse_with_get("desktop.views.index"))
   # Arguments for the view
-  assert_equal("/prefs/foo", reverse_with_get("desktop.views.prefs", kwargs=dict(key="foo")))
+  assert_equal("/desktop/prefs/foo", reverse_with_get("desktop.views.prefs", kwargs=dict(key="foo")))
   # Arguments for the view as well as GET parameters
-  assert_equal("/prefs/foo?a=1&b=2",
+  assert_equal("/desktop/prefs/foo?a=1&b=2",
     reverse_with_get("desktop.views.prefs", kwargs=dict(key="foo"), get=dict(a=1,b=2)))
   # You can use a list of args instead of kwargs, too
-  assert_equal("/prefs/foo?a=1&b=2",
+  assert_equal("/desktop/prefs/foo?a=1&b=2",
     reverse_with_get("desktop.views.prefs", args=["foo"], get=dict(a=1,b=2)))
   # Just GET parameters
   assert_equal("/?a=1", reverse_with_get("desktop.views.index", get=dict(a="1")))

+ 1 - 1
desktop/core/src/desktop/middleware.py

@@ -316,7 +316,7 @@ class LoginAndPermissionMiddleware(object):
       # Here we trust the URL to be the real app we need to check the perms.
       app_accessed = request._desktop_app
       ui_app_accessed = get_app_name(request)
-      if app_accessed != ui_app_accessed and ui_app_accessed not in ('logs', 'dump_config', 'debug'):
+      if app_accessed != ui_app_accessed and ui_app_accessed not in ('logs', 'accounts', 'login'):
         app_accessed = ui_app_accessed
 
       if app_accessed and \

+ 13 - 9
desktop/core/src/desktop/models.py

@@ -127,6 +127,7 @@ class DocumentTagManager(models.Manager):
       doc.add_tag(default_tag)
 
   def update_tags(self, owner, doc_id, tag_ids):
+    # TODO secu
     doc = Document.objects.get_doc(doc_id, owner)
 
     for tag in doc.tags.all():
@@ -166,7 +167,7 @@ class DocumentTag(models.Model):
 class DocumentManager(models.Manager):
 
   def documents(self, user):
-    return Document.objects.filter(Q(owner=user) | Q(documentpermission__users=user) | Q(documentpermission__groups__in=user.groups.all()))
+    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):
     docs = Document.objects.documents(user).exclude(name='pig-app-hue-script')
@@ -321,7 +322,7 @@ class DocumentManager(models.Manager):
         doc.tags.remove(default_tag)
     except Exception, e:
       LOG.warn(force_unicode(e))
-      
+
     # Delete documents with no object
     try:
       for doc in Document.objects.all():
@@ -425,7 +426,7 @@ class Document(models.Model):
       default_tag = DocumentTag.objects.get_default_tag(copy_doc.owner)
       tags = [default_tag]
     copy_doc.tags.add(*tags)
-    
+
     return copy_doc
 
   @property
@@ -437,7 +438,7 @@ class Document(models.Model):
         if self.extra == '0':
           return apps['beeswax'].icon_path
         elif self.extra == '3':
-          return apps['spark'].icon_path        
+          return apps['spark'].icon_path
         else:
           return apps['impala'].icon_path
       elif self.content_type.app_label == 'oozie':
@@ -454,13 +455,15 @@ class Document(models.Model):
       return '/static/art/favicon.png'
 
   def share(self, users, groups, name='read'):
-    DocumentPermission.objects.update(document=self, name=name, users=users, groups=groups, add=True)
+    DocumentPermission.objects.filter(document=self, name=name).update(users=users, groups=groups, add=True)
 
   def unshare(self, users, groups, name='read'):
-    DocumentPermission.objects.update(document=self, name=name, users=users, groups=groups, add=False)
+    DocumentPermission.objects.filter(document=self, name=name).update(users=users, groups=groups, add=False)
 
   def sync_permissions(self, perms_dict):
     """
+    Set who else or which other group can interact with the document.
+
     Example of input: {'read': {'user_ids': [1, 2, 3], 'group_ids': [1, 2, 3]}}
     """
     for name, perm in perms_dict.iteritems():
@@ -518,12 +521,14 @@ class DocumentPermissionManager(models.Manager):
     if users is not None:
       perm.users = []
       perm.users = users
+      perm.save()
 
     if groups is not None:
       perm.groups = []
       perm.groups = groups
+      perm.save()
 
-    if not perm.users and not perm.groups:
+    if not users and not groups:
       perm.delete()
 
   def list(self, document):
@@ -538,8 +543,7 @@ class DocumentPermission(models.Model):
 
   users = models.ManyToManyField(auth_models.User, db_index=True)
   groups = models.ManyToManyField(auth_models.Group, db_index=True)
-  perms = models.TextField(
-      default='read', choices=((READ_PERM, 'read'),),)
+  perms = models.TextField(default=READ_PERM, choices=((READ_PERM, 'read'),))
 
 
   objects = DocumentPermissionManager()

+ 1 - 1
desktop/core/src/desktop/templates/common_header.mako

@@ -287,7 +287,7 @@ from django.utils.translation import ugettext as _
   </div>
     <a class="brand nav-tooltip pull-left" title="${_('About Hue')}" rel="navigator-tooltip" href="/about"><img src="/static/art/hue-logo-mini-white.png" data-orig="/static/art/hue-logo-mini-white.png" data-hover="/static/art/hue-logo-mini-white-hover.png"/></a>
      <ul class="nav nav-pills pull-left">
-       <li><a title="${_('My documents')}" rel="navigator-tooltip" href="/home" style="padding-bottom:2px!important"><i class="fa fa-home" style="font-size: 19px"></i></a></li>
+       <li><a title="${_('My documents')}" rel="navigator-tooltip" href="${ url('desktop.views.home') }" style="padding-bottom:2px!important"><i class="fa fa-home" style="font-size: 19px"></i></a></li>
        <%
          query_apps = count_apps(apps, ['beeswax', 'impala', 'rdbms', 'pig', 'jobsub', 'spark']);
        %>

+ 10 - 10
desktop/core/src/desktop/templates/home.mako

@@ -73,7 +73,7 @@ ${ commonheader(_('Welcome Home'), "home", user) | n,unicode }
       <div class="nav-collapse">
         <ul class="nav">
           <li class="currentApp">
-            <a href="/home">
+            <a href="${ url('desktop.views.home') }">
               <img src="/static/art/home.png" />
               ${ _('My documents') }
             </a>
@@ -249,7 +249,7 @@ var documentsTable;
 
 $(document).ready(function () {
   var selectedUserOrGroup, map, dropdown = null;
-  $.getJSON("${ url('useradmin.views.list_for_autocomplete') }", function (data) {
+  $.getJSON('/desktop/api/users/autocomplete', function (data) {
     JSON_USERS_GROUPS = data;
     dropdown = [];
     map = {};
@@ -484,11 +484,11 @@ $(document).ready(function () {
 
 
   function addTag(value, callback) {
-    $.post("/tag/add_tag", {
+    $.post("/desktop/api/tag/add_tag", {
       name: value
     }, function (data) {
       $("#documentTagsNew").val("");
-      $.getJSON("/tag/list_tags", function (data) {
+      $.getJSON("/desktop/api/tag/list_tags", function (data) {
         JSON_TAGS = data;
         renderTags();
         callback();
@@ -534,7 +534,7 @@ $(document).ready(function () {
       var _this = $(this);
       _tags.push(_this.data("value"));
     });
-    $.post("/doc/update_tags", {
+    $.post("/desktop/api/doc/update_tags", {
       data: JSON.stringify({
         doc_id: $("#documentTagsModal").data("document-id"),
         tag_ids: _tags
@@ -569,7 +569,7 @@ $(document).ready(function () {
       var _this = $(this);
       _tags.push(_this.data("value"));
     });
-    $.post("/tag/remove_tags", {
+    $.post("/desktop/api/tag/remove_tags", {
       data: JSON.stringify({
         tag_ids: _tags
       })
@@ -577,7 +577,7 @@ $(document).ready(function () {
         if (response!=null){
           if (response.status == 0){
             $(document).trigger("info", response.message);
-            $.getJSON("/tag/list_tags", function (data) {
+            $.getJSON("/desktop/api/tag/list_tags", function (data) {
               JSON_TAGS = data;
               renderTags();
               renderTagsModal();
@@ -682,7 +682,7 @@ $(document).ready(function () {
       }
     }
 
-    $.post("/doc/update_permissions", {
+    $.post("/desktop/api/doc/update_permissions", {
       doc_id: $("#documentShareModal").data("document-id"),
       data: JSON.stringify(_postPerms)
     }, function (response) {
@@ -693,7 +693,7 @@ $(document).ready(function () {
           updateDoc(response.doc);
         }
         else {
-          $(document).trigger("error", "${_("There was an error processing your action: ")}"+response.message);
+          $(document).trigger("error", "${_("There was an error processing your action: ")}" + response.message);
         }
       }
     });
@@ -702,7 +702,7 @@ $(document).ready(function () {
 });
 
 function renderDocs(callback) {
-  $.getJSON("/doc/list_docs", function (data) {
+  $.getJSON("/desktop/api/doc/list_docs", function (data) {
     JSON_DOCS = data;
     populateTable();
     if (callback != null) {

+ 1 - 1
desktop/core/src/desktop/templates/logs.mako

@@ -69,7 +69,7 @@ ${layout.menubar(section='log_view')}
       </%def>
       <%def name="creation()">
         <label class="checkbox" style="display: inline-block; margin-right: 10px"><input id="wrapLogs" type="checkbox" checked="checked">${_('Wrap logs')}</label>
-        <a href="/download_logs" class="btn"><i class="fa fa-download"></i> ${_('Download entire log as zip')}</a>
+        <a href="/desktop/download_logs" class="btn"><i class="fa fa-download"></i> ${_('Download entire log as zip')}</a>
       </%def>
     </%actionbar:render>
 

+ 41 - 137
desktop/core/src/desktop/tests.py

@@ -20,7 +20,6 @@ import desktop
 import desktop.urls
 import desktop.conf
 import logging
-import json
 import os
 import time
 
@@ -36,6 +35,8 @@ from django.core.urlresolvers import reverse
 from django.http import HttpResponse
 from django.db.models import query, CharField, SmallIntegerField
 
+from useradmin.models import GroupPermission
+
 from desktop.lib import django_mako
 from desktop.lib.django_test_util import make_logged_in_client
 from desktop.lib.paginator import Paginator
@@ -44,9 +45,6 @@ from desktop.lib.django_util import TruncatingModel
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.test_utils import grant_access
 from desktop.views import check_config, home
-from desktop.models import DocumentTag , Document
-from pig.models import PigScript
-from useradmin.models import GroupPermission
 
 
 def setup_test_environment():
@@ -157,10 +155,10 @@ def test_dump_config():
   CANARY = "abracadabra"
   clear = desktop.conf.HTTP_HOST.set_for_testing(CANARY)
 
-  response1 = c.get('/dump_config')
+  response1 = c.get(reverse('desktop.views.dump_config'))
   assert_true(CANARY in response1.content)
 
-  response2 = c.get('/dump_config', dict(private="true"))
+  response2 = c.get(reverse('desktop.views.dump_config'), dict(private="true"))
   assert_true(CANARY in response2.content)
 
   # There are more private variables...
@@ -171,7 +169,7 @@ def test_dump_config():
   CANARY = "(localhost|127\.0\.0\.1):(50030|50070|50060|50075)"
   clear = proxy.conf.WHITELIST.set_for_testing(CANARY)
 
-  response1 = c.get('/dump_config')
+  response1 = c.get(reverse('desktop.views.dump_config'))
   assert_true(CANARY in response1.content)
 
   clear()
@@ -180,7 +178,7 @@ def test_dump_config():
   CANARY = "asdfoijaoidfjaosdjffjfjaoojosjfiojdosjoidjfoa"
   clear = desktop.conf.HTTP_PORT.set_for_testing(CANARY)
 
-  response1 = c.get('/dump_config')
+  response1 = c.get(reverse('desktop.views.dump_config'))
   assert_true(CANARY in response1.content, response1.content)
 
   clear()
@@ -188,7 +186,7 @@ def test_dump_config():
   CANARY = '/tmp/spacé.dat'
   finish = proxy.conf.WHITELIST.set_for_testing(CANARY)
   try:
-    response = c.get('/dump_config')
+    response = c.get(reverse('desktop.views.dump_config'))
     assert_true(CANARY in response.content, response.content)
   finally:
     finish()
@@ -197,11 +195,11 @@ def test_dump_config():
   client_not_me = make_logged_in_client(username='not_me', is_superuser=False, groupname='test')
   grant_access("not_me", "test", "desktop")
 
-  response = client_not_me.get('/dump_config')
+  response = client_not_me.get(reverse('desktop.views.dump_config'))
   assert_true("You must be a superuser" in response.content, response.content)
 
   os.environ["HUE_CONF_DIR"] = "/tmp/test_hue_conf_dir"
-  resp = c.get('/dump_config')
+  resp = c.get(reverse('desktop.views.dump_config'))
   del os.environ["HUE_CONF_DIR"]
   assert_true('/tmp/test_hue_conf_dir' in resp.content, resp)
 
@@ -210,34 +208,34 @@ def test_prefs():
   c = make_logged_in_client()
 
   # Get everything
-  response = c.get('/prefs/')
+  response = c.get('/desktop/prefs/')
   assert_equal('{}', response.content)
 
   # Set and get
-  response = c.get('/prefs/foo', dict(set="bar"))
+  response = c.get('/desktop/prefs/foo', dict(set="bar"))
   assert_equal('true', response.content)
-  response = c.get('/prefs/foo')
+  response = c.get('/desktop/prefs/foo')
   assert_equal('"bar"', response.content)
 
   # Reset (use post this time)
-  c.post('/prefs/foo', dict(set="baz"))
-  response = c.get('/prefs/foo')
+  c.post('/desktop/prefs/foo', dict(set="baz"))
+  response = c.get('/desktop/prefs/foo')
   assert_equal('"baz"', response.content)
 
   # Check multiple values
-  c.post('/prefs/elephant', dict(set="room"))
-  response = c.get('/prefs/')
+  c.post('/desktop/prefs/elephant', dict(set="room"))
+  response = c.get('/desktop/prefs/')
   assert_true("baz" in response.content)
   assert_true("room" in response.content)
 
   # Delete everything
-  c.get('/prefs/elephant', dict(delete=""))
-  c.get('/prefs/foo', dict(delete=""))
-  response = c.get('/prefs/')
+  c.get('/desktop/prefs/elephant', dict(delete=""))
+  c.get('/desktop/prefs/foo', dict(delete=""))
+  response = c.get('/desktop/prefs/')
   assert_equal('{}', response.content)
 
   # Check non-existent value
-  response = c.get('/prefs/doesNotExist')
+  response = c.get('/desktop/prefs/doesNotExist')
   assert_equal('null', response.content)
 
 def test_status_bar():
@@ -256,7 +254,7 @@ def test_status_bar():
     raise Exception()
   views.register_status_bar_view(f)
 
-  response = c.get("/status_bar")
+  response = c.get("/desktop/status_bar")
   assert_equal("foobar", response.content)
 
   views._status_bar_views = backup
@@ -305,7 +303,7 @@ def test_paginator():
 
 def test_thread_dump():
   c = make_logged_in_client()
-  response = c.get("/debug/threads")
+  response = c.get("/desktop/debug/threads")
   assert_true("test_thread_dump" in response.content)
 
 def test_truncating_model():
@@ -373,6 +371,18 @@ def test_error_handling():
     restore_500_debug()
 
 
+def test_desktop_permissions():
+  USERNAME = 'test_core_permissions'
+  GROUPNAME = 'default'
+
+  c = make_logged_in_client(USERNAME, groupname=GROUPNAME, recreate=True, is_superuser=False)
+
+  # Access to the basic works
+  assert_equal(200, c.get('/accounts/login/', follow=True).status_code)
+  assert_equal(200, c.get('/accounts/logout', follow=True).status_code)
+  assert_equal(200, c.get('/home', follow=True).status_code)
+
+
 def test_app_permissions():
   USERNAME = 'test_app_permissions'
   GROUPNAME = 'impala_only'
@@ -438,7 +448,7 @@ def test_error_handling_failure():
   try:
     # Make sure we are showing default 500.html page.
     # See django.test.client#L246
-    assert_raises(AttributeError, c.get, '/dump_config')
+    assert_raises(AttributeError, c.get, reverse('desktop.views.dump_config'))
   finally:
     # Restore the world
     restore_django_debug()
@@ -468,20 +478,20 @@ def test_log_event():
   handler = RecordingHandler()
   root.addHandler(handler)
 
-  c.get("/log_frontend_event?level=info&message=foo")
+  c.get("/desktop/log_frontend_event?level=info&message=foo")
   assert_equal("INFO", handler.records[-1].levelname)
   assert_equal("Untrusted log event from user test: foo", handler.records[-1].message)
   assert_equal("desktop.views.log_frontend_event", handler.records[-1].name)
 
-  c.get("/log_frontend_event?level=error&message=foo2")
+  c.get("/desktop/log_frontend_event?level=error&message=foo2")
   assert_equal("ERROR", handler.records[-1].levelname)
   assert_equal("Untrusted log event from user test: foo2", handler.records[-1].message)
 
-  c.get("/log_frontend_event?message=foo3")
+  c.get("/desktop/log_frontend_event?message=foo3")
   assert_equal("INFO", handler.records[-1].levelname)
   assert_equal("Untrusted log event from user test: foo3", handler.records[-1].message)
 
-  c.post("/log_frontend_event", {
+  c.post("/desktop/log_frontend_event", {
     "message": "01234567" * 1024})
   assert_equal("INFO", handler.records[-1].levelname)
   assert_equal("Untrusted log event from user test: " + "01234567"*(1024/8),
@@ -509,7 +519,7 @@ def test_config_check():
 
   try:
     cli = make_logged_in_client()
-    resp = cli.get('/debug/check_config')
+    resp = cli.get('/desktop/debug/check_config')
     assert_true('Secret key should be configured' in resp.content, resp)
     assert_true('desktop.ssl_certificate' in resp.content, resp)
     assert_true('Path does not exist' in resp.content, resp)
@@ -519,7 +529,7 @@ def test_config_check():
 
     # Set HUE_CONF_DIR and make sure check_config returns appropriate conf
     os.environ["HUE_CONF_DIR"] = "/tmp/test_hue_conf_dir"
-    resp = cli.get('/debug/check_config')
+    resp = cli.get('/desktop/debug/check_config')
     del os.environ["HUE_CONF_DIR"]
     assert_true('/tmp/test_hue_conf_dir' in resp.content, resp)
   finally:
@@ -567,109 +577,3 @@ def test_check_config_ajax():
   c = make_logged_in_client()
   response = c.get(reverse(check_config))
   assert_true("misconfiguration" in response.content, response.content)
-
-
-class TestDocModelTags():
-  def setUp(self):
-    self.client = make_logged_in_client(username="tag_user")
-    self.client_not_me = make_logged_in_client(username="not_tag_user")
-
-    self.user = User.objects.get(username="tag_user")
-    self.user_not_me = User.objects.get(username="not_tag_user")
-
-    grant_access(self.user.username, self.user.username, "desktop")
-    grant_access(self.user_not_me.username, self.user_not_me.username, "desktop")
-
-  def add_tag(self, name):
-    response = self.client.post("/tag/add_tag", {'name': name})
-    assert_equal(0, json.loads(response.content)['status'], response.content)
-    return json.loads(response.content)['tag_id']
-
-  def add_doc(self, name):
-    script = PigScript.objects.create(owner=self.user)
-    doc = Document.objects.link(script, owner=script.owner, name=name)
-    return script, doc
-
-  def test_add_tag(self):
-    response = self.client.get("/tag/add_tag")
-    assert_equal(-1, json.loads(response.content)['status'])
-
-    tag_id = self.add_tag('my_tag')
-
-    assert_true(DocumentTag.objects.filter(id=tag_id, owner=self.user, tag='my_tag').exists())
-
-  def test_remove_tags(self):
-    response = self.client.post("/tag/add_tag", {'name': 'my_tag'})
-    tag_id = json.loads(response.content)['tag_id']
-
-    response = self.client.get("/tag/remove_tags")
-    assert_equal(-1, json.loads(response.content)['status'])
-
-    response = self.client_not_me.post("/tag/remove_tags", {'data': json.dumps({'tag_ids': [tag_id]})})
-    assert_equal(-1, json.loads(response.content)['status'], response.content)
-
-    response = self.client.post("/tag/remove_tags", {'data': json.dumps({'tag_ids': [tag_id]})})
-    assert_equal(0, json.loads(response.content)['status'], response.content)
-
-    assert_false(DocumentTag.objects.filter(id=tag_id).exists())
-
-  def test_list_tags(self):
-    tag_id = self.add_tag('my_list_tags')
-
-    response = self.client.get("/tag/list_tags")
-    assert_true([tag for tag in json.loads(response.content) if tag['id'] == tag_id], response.content)
-
-  def test_list_docs(self):
-    script, doc = self.add_doc('test-pig')
-
-    response = self.client.get("/doc/list_docs")
-    assert_true([doc for doc in json.loads(response.content) if doc['id'] == script.id], response.content)
-
-  def test_tag(self):
-    script, doc = self.add_doc('tag_pig')
-
-    response = self.client.post("/doc/tag", {'data': json.dumps({'doc_id': doc.id, 'tag': 'pig'})})
-    assert_equal(0, json.loads(response.content)['status'], response.content)
-
-    tag2_id = self.add_tag('pig2')
-
-    response = self.client.post("/doc/tag", {'data': json.dumps({'doc_id': doc.id, 'tag_id': tag2_id})})
-    assert_equal(0, json.loads(response.content)['status'], response.content)
-
-  def test_update_tags(self):
-    script, doc = self.add_doc('update_tags')
-
-    tag1_id = self.add_tag('update_tags_1')
-    tag2_id = self.add_tag('update_tags_2')
-
-    response = self.client.post("/doc/update_tags", {'data': json.dumps({'doc_id': doc.id, 'tag_ids': [tag1_id, tag2_id]})})
-    assert_equal(0, json.loads(response.content)['status'], response.content)
-
-
-
-class TestDocModelPermissions():
-
-  def setUp(self):
-    self.client = make_logged_in_client(username="perm_user")
-    self.client_not_me = make_logged_in_client(username="not_perm_user")
-
-    self.user = User.objects.get(username="perm_user")
-    self.user_not_me = User.objects.get(username="not_perm_user")
-
-    grant_access(self.user.username, self.user.username, "desktop")
-    grant_access(self.user_not_me.username, self.user_not_me.username, "desktop")
-
-  def add_doc(self, name):
-    script = PigScript.objects.create(owner=self.user)
-    doc = Document.objects.link(script, owner=script.owner, name=name)
-    return script, doc
-
-  def test_update_permissions(self):
-    script, doc = self.add_doc('test_update_permissions')
-
-    response = self.client.post("/doc/update_permissions", {
-        'doc_id': doc.id,
-        'data': json.dumps({'read': {'user_ids': [1, 2], 'group_ids': [1]}})
-    })
-
-    assert_equal(0, json.loads(response.content)['status'], response.content)

+ 32 - 27
desktop/core/src/desktop/urls.py

@@ -38,8 +38,7 @@ def static_pattern(urlprefix, root):
   First argument is the url mapping, and second argument is the
   directory to serve.
   """
-  return (r'^%s/(?P<path>.*)$' % urlprefix, 'django.views.static.serve',
-   { 'document_root': root, 'show_indexes': False })
+  return (r'^%s/(?P<path>.*)$' % urlprefix, 'django.views.static.serve', { 'document_root': root, 'show_indexes': False })
 
 
 admin.autodiscover()
@@ -48,36 +47,25 @@ admin.autodiscover()
 dynamic_patterns = patterns('desktop.auth.views',
   (r'^accounts/login/$', 'dt_login'),
   (r'^accounts/logout/$', 'dt_logout', {'next_page': '/'}),
-  (r'^profile$', 'profile'),  
+  (r'^profile$', 'profile'),
   (r'^login/oauth/?$', 'oauth_login'),
-  (r'^login/oauth_authenticated/?$', 'oauth_authenticated'),  
+  (r'^login/oauth_authenticated/?$', 'oauth_authenticated'),
 )
-  
+
 dynamic_patterns += patterns('desktop.views',
   (r'^logs$','log_view'),
   (r'^home$','home'),
-  (r'^dump_config$','dump_config'),
-  (r'^download_logs$','download_log_view'),
-  (r'^bootstrap.js$', 'bootstrap'),
-  
-  (r'^prefs/(?P<key>\w+)?$', 'prefs'),
-  (r'^status_bar/?$', 'status_bar'),
-  (r'^debug/threads$', 'threads'),
-  (r'^debug/who_am_i$', 'who_am_i'),
-  (r'^debug/check_config$', 'check_config'),
-  (r'^debug/check_config_ajax$', 'check_config_ajax'),
-  (r'^log_frontend_event$', 'log_frontend_event'),
-
-  # Tags
-  (r'^tag/add_tag$', 'add_tag'),
-  (r'^tag/remove_tags$', 'remove_tags'),
-  (r'^tag/list_tags$', 'list_tags'),
-  (r'^doc/list_docs$', 'list_docs'),
-  (r'^doc/tag$', 'tag'),
-  (r'^doc/update_tags$', 'update_tags'),
-
-  # Permissions
-  (r'^doc/update_permissions', 'update_permissions'),
+  (r'^desktop/dump_config$','dump_config'),
+  (r'^desktop/download_logs$','download_log_view'),
+  (r'^bootstrap.js$', 'bootstrap'), # unused
+
+  (r'^desktop/prefs/(?P<key>\w+)?$', 'prefs'),
+  (r'^desktop/status_bar/?$', 'status_bar'),
+  (r'^desktop/debug/threads$', 'threads'),
+  (r'^desktop/debug/who_am_i$', 'who_am_i'),
+  (r'^desktop/debug/check_config$', 'check_config'),
+  (r'^desktop/debug/check_config_ajax$', 'check_config_ajax'),
+  (r'^desktop/log_frontend_event$', 'log_frontend_event'),
 
   # Jasmine
   (r'^jasmine', 'jasmine'),
@@ -86,6 +74,23 @@ dynamic_patterns += patterns('desktop.views',
   (r'^$', 'index'),
 )
 
+dynamic_patterns += patterns('desktop.api',
+  # Tags
+  (r'^desktop/api/tag/add_tag$', 'add_tag'),
+  (r'^desktop/api/tag/remove_tags$', 'remove_tags'),
+  (r'^desktop/api/tag/list_tags$', 'list_tags'),
+  (r'^desktop/api/doc/list_docs$', 'list_docs'),
+  (r'^desktop/api/doc/tag$', 'tag'),
+  (r'^desktop/api/doc/update_tags$', 'update_tags'),
+
+  # Permissions
+  (r'^desktop/api/doc/update_permissions', 'update_permissions'),
+)
+
+dynamic_patterns += patterns('useradmin.views',
+  (r'^desktop/api/users/autocomplete', 'list_for_autocomplete'),
+)
+
 dynamic_patterns += patterns('',
   (r'^admin/', include(admin.site.urls)),
 )

+ 3 - 143
desktop/core/src/desktop/views.py

@@ -36,13 +36,14 @@ import django.views.debug
 from desktop.lib import django_mako
 from desktop.lib.conf import GLOBAL_CONFIG
 from desktop.lib.django_util import login_notrequired, render_json, render
-from desktop.lib.i18n import smart_str, force_unicode
+from desktop.lib.i18n import smart_str
 from desktop.lib.paths import get_desktop_root
 from desktop.log.access import access_log_level, access_warn
 from desktop.models import UserPreferences, Settings, Document, DocumentTag
 from desktop import appmanager
 import desktop.conf
 import desktop.log.log_buffer
+from desktop.api import massaged_tags_for_json, massaged_documents_for_json
 
 
 LOG = logging.getLogger(__name__)
@@ -63,147 +64,6 @@ def home(request):
   })
 
 
-def list_docs(request):
-  docs = Document.objects.get_docs(request.user).order_by('-last_modified')[:1000]
-  return HttpResponse(json.dumps(massaged_documents_for_json(docs)), mimetype="application/json")
-
-
-def list_tags(request):
-  tags = DocumentTag.objects.get_tags(user=request.user)
-  return HttpResponse(json.dumps(massaged_tags_for_json(tags, request.user)), mimetype="application/json")
-
-
-def massaged_documents_for_json(documents):
-  return [massage_doc_for_json(doc) for doc in documents]
-
-
-def massage_doc_for_json(doc):
-  perms = doc.list_permissions()
-  return {
-      'id': doc.id,
-      'contentType': doc.content_type.name,
-      'icon': doc.icon,
-      'name': doc.name,
-      'url': doc.content_object.get_absolute_url(),
-      'description': doc.description,
-      'tags': [{'id': tag.id, 'name': tag.tag} for tag in doc.tags.all()],
-      'perms': {
-        'read': {
-          'users': [{'id': user.id, 'username': user.username} for user in perms.users.all()],
-          'groups': [{'id': group.id, 'name': group.name} for group in perms.groups.all()]
-        }
-      },
-      'owner': doc.owner.username,
-      'lastModified': doc.last_modified.strftime("%x %X"),
-      'lastModifiedInMillis': time.mktime(doc.last_modified.timetuple())
-    }
-
-def massaged_tags_for_json(tags, user):
-  ts = []
-  trash = DocumentTag.objects.get_trash_tag(user)
-  history = DocumentTag.objects.get_history_tag(user)
-
-  for tag in tags:
-    massaged_tag = {
-      'id': tag.id,
-      'name': tag.tag,
-      'isTrash': tag.id == trash.id,
-      'isHistory': tag.id == history.id,
-      'isExample': tag.tag == DocumentTag.EXAMPLE
-    }
-    ts.append(massaged_tag)
-
-  return ts
-
-def add_tag(request):
-  response = {'status': -1, 'message': ''}
-
-  if request.method == 'POST':
-    try:
-      tag = DocumentTag.objects.create_tag(request.user, request.POST['name'])
-      response['tag_id'] = tag.id
-      response['status'] = 0
-    except Exception, e:
-      response['message'] = force_unicode(e)
-  else:
-    response['message'] = _('POST request only')
-
-  return HttpResponse(json.dumps(response), mimetype="application/json")
-
-
-def tag(request):
-  response = {'status': -1, 'message': ''}
-
-  if request.method == 'POST':
-    request_json = json.loads(request.POST['data'])
-    try:
-      tag = DocumentTag.objects.tag(request.user, request_json['doc_id'], request_json.get('tag'), request_json.get('tag_id'))
-      response['tag_id'] = tag.id
-      response['status'] = 0
-    except Exception, e:
-      response['message'] = force_unicode(e)
-  else:
-    response['message'] = _('POST request only')
-
-  return HttpResponse(json.dumps(response), mimetype="application/json")
-
-
-def update_tags(request):
-  response = {'status': -1, 'message': ''}
-
-  if request.method == 'POST':
-    request_json = json.loads(request.POST['data'])
-    try:
-      doc = DocumentTag.objects.update_tags(request.user, request_json['doc_id'], request_json['tag_ids'])
-      response['doc'] = massage_doc_for_json(doc)
-      response['status'] = 0
-    except Exception, e:
-      response['message'] = force_unicode(e)
-  else:
-    response['message'] = _('POST request only')
-
-  return HttpResponse(json.dumps(response), mimetype="application/json")
-
-
-def remove_tags(request):
-  response = {'status': -1, 'message': _('Error')}
-
-  if request.method == 'POST':
-    request_json = json.loads(request.POST['data'])
-    try:
-      for tag_id in request_json['tag_ids']:
-        DocumentTag.objects.delete_tag(tag_id, request.user)
-      response['message'] = _('Tag(s) removed!')
-      response['status'] = 0
-    except Exception, e:
-      response['message'] = force_unicode(e)
-  else:
-    response['message'] = _('POST request only')
-
-  return HttpResponse(json.dumps(response), mimetype="application/json")
-
-
-def update_permissions(request):
-  response = {'status': -1, 'message': _('Error')}
-
-  if request.method == 'POST':
-    data = json.loads(request.POST['data'])
-    doc_id = request.POST['doc_id']
-    try:
-      doc = Document.objects.get_doc(doc_id, request.user)
-      # doc.sync_permissions({'read': {'user_ids': [1, 2, 3], 'group_ids': [1, 2, 3]}})
-      doc.sync_permissions(data)
-      response['message'] = _('Permissions updated!')
-      response['status'] = 0
-      response['doc'] = massage_doc_for_json(doc)
-    except Exception, e:
-      response['message'] = force_unicode(e)
-  else:
-    response['message'] = _('POST request only')
-
-  return HttpResponse(json.dumps(response), mimetype="application/json")
-
-
 @access_log_level(logging.WARN)
 def log_view(request):
   """
@@ -257,7 +117,7 @@ def download_log_view(request):
         response['Content-Length'] = length
         return response
       except Exception, e:
-        logging.exception("Couldn't construct zip file to write logs to.")
+        logging.exception("Couldn't construct zip file to write logs to: %s") % e
         return log_view(request)
 
   return render_to_response("logs.mako", dict(log=[_("No logs found.")]))

+ 1 - 1
desktop/libs/hadoop/src/hadoop/tests.py

@@ -119,7 +119,7 @@ def test_config_validator_basic():
   old = cluster.clear_caches()
   try:
     cli = make_logged_in_client()
-    resp = cli.get('/debug/check_config')
+    resp = cli.get('/desktop/debug/check_config')
     assert_true('hadoop.hdfs_clusters.default.webhdfs_url' in resp.content)
   finally:
     for old_conf in reset: