浏览代码

[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 年之前
父节点
当前提交
100e22a

+ 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: