api.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. #!/usr/bin/env python
  2. # Licensed to Cloudera, Inc. under one
  3. # or more contributor license agreements. See the NOTICE file
  4. # distributed with this work for additional information
  5. # regarding copyright ownership. Cloudera, Inc. licenses this file
  6. # to you under the Apache License, Version 2.0 (the
  7. # "License"); you may not use this file except in compliance
  8. # with the License. You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. import logging
  18. import json
  19. import time
  20. from collections import defaultdict
  21. from django.utils import html
  22. from django.utils.translation import ugettext as _
  23. from django.views.decorators.http import require_GET, require_POST
  24. import desktop.conf
  25. from desktop.lib.django_util import JsonResponse
  26. from desktop.lib.i18n import force_unicode
  27. from desktop.models import Document, DocumentTag, Document2, Directory
  28. LOG = logging.getLogger(__name__)
  29. def _get_docs(user):
  30. history_tag = DocumentTag.objects.get_history_tag(user)
  31. dir_ids = [directory.doc.get().id for directory in Directory.objects.filter(doc__isnull=False)]
  32. editor_ids = [document.doc.get().id for document in Document2.objects.filter(type__startswith='query', doc__isnull=False)]
  33. query = Document.objects.get_docs(user) \
  34. .exclude(tags__in=[history_tag]) \
  35. .exclude(id__in=dir_ids) \
  36. .exclude(id__in=editor_ids)
  37. # Work around Oracle not supporting SELECT DISTINCT with the CLOB type.
  38. if desktop.conf.DATABASE.ENGINE.get() == 'django.db.backends.oracle':
  39. query = query.only('id')
  40. else:
  41. query = query.defer(None)
  42. docs = query.order_by('-last_modified')[:100]
  43. if desktop.conf.DATABASE.ENGINE.get() == 'django.db.backends.oracle':
  44. ids = [doc.id for doc in docs]
  45. docs = Document.objects.filter(id__in=ids).defer(None)
  46. docs = docs \
  47. .select_related('owner', 'content_type') \
  48. .prefetch_related('tags', 'documentpermission_set')
  49. return docs
  50. def massaged_tags_for_json(docs, user):
  51. """
  52. var TAGS_DEFAULTS = {
  53. 'history': {'name': 'History', 'id': 1, 'docs': [1], 'type': 'history'},
  54. 'trash': {'name': 'Trash', 'id': 3, 'docs': [2]},
  55. 'mine': [{'name': 'default', 'id': 2, 'docs': [3]}, {'name': 'web', 'id': 3, 'docs': [3]}],
  56. 'notmine': [{'name': 'example', 'id': 20, 'docs': [10]}, {'name': 'ex2', 'id': 30, 'docs': [10, 11]}]
  57. };
  58. """
  59. ts = {
  60. 'trash': {},
  61. 'history': {},
  62. 'mine': [],
  63. 'notmine': [],
  64. }
  65. sharers = defaultdict(list)
  66. trash_tag = DocumentTag.objects.get_trash_tag(user)
  67. history_tag = DocumentTag.objects.get_history_tag(user)
  68. tag_doc_mapping = defaultdict(set) # List of documents available in each tag
  69. for doc in docs:
  70. for tag in doc.tags.all():
  71. tag_doc_mapping[tag].add(doc)
  72. ts['trash'] = massaged_tags(trash_tag, tag_doc_mapping)
  73. ts['history'] = massaged_tags(history_tag, tag_doc_mapping)
  74. tags = list(set(tag_doc_mapping.keys() + [tag for tag in DocumentTag.objects.get_tags(user=user)])) # List of all personal and shared tags
  75. for tag in tags:
  76. massaged_tag = massaged_tags(tag, tag_doc_mapping)
  77. if tag == trash_tag:
  78. ts['trash'] = massaged_tag
  79. elif tag == history_tag:
  80. ts['history'] = massaged_tag
  81. elif tag.owner == user:
  82. ts['mine'].append(massaged_tag)
  83. else:
  84. sharers[tag.owner].append(massaged_tag)
  85. ts['notmine'] = [{'name': sharer.username, 'projects': projects} for sharer, projects in sharers.iteritems()]
  86. # Remove from my tags the trashed and history ones
  87. mine_filter = set(ts['trash']['docs'] + ts['history']['docs'])
  88. for tag in ts['mine']:
  89. tag['docs'] = [doc_id for doc_id in tag['docs'] if doc_id not in mine_filter]
  90. return ts
  91. def massaged_tags(tag, tag_doc_mapping):
  92. return {
  93. 'id': tag.id,
  94. 'name': html.conditional_escape(tag.tag),
  95. 'owner': tag.owner.username,
  96. 'docs': [doc.id for doc in tag_doc_mapping[tag]] # Could get with one request groupy
  97. }
  98. def massage_permissions(document):
  99. """
  100. Returns the permissions for a given document as a dictionary
  101. """
  102. read_perms = document.list_permissions(perm='read')
  103. write_perms = document.list_permissions(perm='write')
  104. return {
  105. 'perms': {
  106. 'read': {
  107. 'users': [{'id': perm_user.id, 'username': perm_user.username} \
  108. for perm_user in read_perms.users.all()],
  109. 'groups': [{'id': perm_group.id, 'name': perm_group.name} \
  110. for perm_group in read_perms.groups.all()]
  111. },
  112. 'write': {
  113. 'users': [{'id': perm_user.id, 'username': perm_user.username} \
  114. for perm_user in write_perms.users.all()],
  115. 'groups': [{'id': perm_group.id, 'name': perm_group.name} \
  116. for perm_group in write_perms.groups.all()]
  117. }
  118. }
  119. }
  120. def massaged_documents_for_json(documents, user):
  121. """
  122. var DOCUMENTS_DEFAULTS = {
  123. '1': {
  124. 'id': 1,
  125. 'name': 'my query history', 'description': '', 'url': '/beeswax/execute/design/83', 'icon': '/static/beeswax/art/icon_beeswax_24.png',
  126. 'lastModified': '03/11/14 16:06:49', 'owner': 'admin', 'lastModifiedInMillis': 1394579209.0, 'isMine': true
  127. },
  128. '2': {
  129. 'id': 2,
  130. 'name': 'my query 2 trashed', 'description': '', 'url': '/beeswax/execute/design/83', 'icon': '/static/beeswax/art/icon_beeswax_24.png',
  131. 'lastModified': '03/11/14 16:06:49', 'owner': 'admin', 'lastModifiedInMillis': 1394579209.0, 'isMine': true
  132. },
  133. '3': {
  134. 'id': 3,
  135. 'name': 'my query 3 tagged twice', 'description': '', 'url': '/beeswax/execute/design/83', 'icon': '/static/beeswax/art/icon_beeswax_24.png',
  136. 'lastModified': '03/11/14 16:06:49', 'owner': 'admin', 'lastModifiedInMillis': 1394579209.0, 'isMine': true
  137. },
  138. '10': {
  139. 'id': 10,
  140. 'name': 'my query 3 shared', 'description': '', 'url': '/beeswax/execute/design/83', 'icon': '/static/beeswax/art/icon_beeswax_24.png',
  141. 'lastModified': '03/11/14 16:06:49', 'owner': 'admin', 'lastModifiedInMillis': 1394579209.0, 'isMine': true
  142. },
  143. '11': {
  144. 'id': 11,
  145. 'name': 'my query 4 shared', 'description': '', 'url': '/beeswax/execute/design/83', 'icon': '/static/beeswax/art/icon_beeswax_24.png',
  146. 'lastModified': '03/11/14 16:06:49', 'owner': 'admin', 'lastModifiedInMillis': 1394579209.0, 'isMine': true
  147. }
  148. };
  149. """
  150. docs = {}
  151. for document in documents:
  152. try:
  153. url = document.content_object and hasattr(document.content_object, 'get_absolute_url') and document.content_object.get_absolute_url() or ''
  154. except:
  155. LOG.exception('failed to get absolute url')
  156. # If app of document is disabled
  157. url = ''
  158. docs[document.id] = massage_doc_for_json(document, user, url)
  159. return docs
  160. @require_GET
  161. def get_document(request):
  162. response = {'status': -1, 'message': ''}
  163. doc_id = request.GET.get('id', '')
  164. if doc_id.isdigit():
  165. doc = None
  166. try:
  167. doc = Document.objects.get(id=doc_id)
  168. except Document.DoesNotExist:
  169. pass
  170. if doc and doc.can_read(request.user):
  171. response = massage_doc_for_json(doc, request.user)
  172. else:
  173. response['message'] = _('get_document requires read priviledge or document does not exist for: %s') % doc_id
  174. else:
  175. response['message'] = _('get_document requires an id integer parameter: %s') % doc_id
  176. return JsonResponse(response)
  177. def massage_doc_for_json(document, user, url=''):
  178. read_perms = document.list_permissions(perm='read')
  179. write_perms = document.list_permissions(perm='write')
  180. massaged_doc = {
  181. 'id': document.id,
  182. 'contentType': html.conditional_escape(document.content_type.name),
  183. 'icon': document.icon,
  184. 'name': html.conditional_escape(document.name),
  185. 'url': html.conditional_escape(url),
  186. 'description': html.conditional_escape(document.description),
  187. 'tags': [{'id': tag.id, 'name': html.conditional_escape(tag.tag)} \
  188. for tag in document.tags.all()],
  189. 'owner': document.owner.username,
  190. 'isMine': document.owner == user,
  191. 'lastModified': document.last_modified.strftime("%x %X"),
  192. 'lastModifiedInMillis': time.mktime(document.last_modified.timetuple())
  193. }
  194. permissions = massage_permissions(document)
  195. massaged_doc.update(permissions)
  196. return massaged_doc
  197. def valid_project(name):
  198. project_doc = DocumentTag.objects.filter(tag=name)
  199. return len(project_doc) == 0
  200. @require_POST
  201. def add_tag(request):
  202. response = {'status': -1, 'message': ''}
  203. try:
  204. validstatus = valid_project(name=request.POST['name'])
  205. if validstatus:
  206. tag = DocumentTag.objects.create_tag(request.user, request.POST['name'])
  207. response['name'] = request.POST['name']
  208. response['id'] = tag.id
  209. response['docs'] = []
  210. response['owner'] = request.user.username
  211. response['status'] = 0
  212. else:
  213. response['status'] = -1
  214. except KeyError, e:
  215. response['message'] = _('Form is missing %s field') % e.message
  216. except Exception, e:
  217. response['message'] = force_unicode(e)
  218. return JsonResponse(response)
  219. @require_POST
  220. def tag(request):
  221. response = {'status': -1, 'message': ''}
  222. request_json = json.loads(request.POST['data'])
  223. try:
  224. tag = DocumentTag.objects.tag(request.user, request_json['doc_id'], request_json.get('tag'), request_json.get('tag_id'))
  225. response['tag_id'] = tag.id
  226. response['status'] = 0
  227. except KeyError, e:
  228. response['message'] = _('Form is missing %s field') % e.message
  229. except Exception, e:
  230. response['message'] = force_unicode(e)
  231. return JsonResponse(response)
  232. @require_POST
  233. def update_tags(request):
  234. response = {'status': -1, 'message': ''}
  235. request_json = json.loads(request.POST['data'])
  236. try:
  237. doc = DocumentTag.objects.update_tags(request.user, request_json['doc_id'], request_json['tag_ids'])
  238. response['doc'] = massage_doc_for_json(doc, request.user)
  239. response['status'] = 0
  240. except KeyError, e:
  241. response['message'] = _('Form is missing %s field') % e.message
  242. except Exception, e:
  243. response['message'] = force_unicode(e)
  244. return JsonResponse(response)
  245. @require_POST
  246. def remove_tag(request):
  247. response = {'status': -1, 'message': _('Error')}
  248. try:
  249. DocumentTag.objects.delete_tag(request.POST['tag_id'], request.user)
  250. response['message'] = _('Project removed!')
  251. response['status'] = 0
  252. except KeyError, e:
  253. response['message'] = _('Form is missing %s field') % e.message
  254. except Exception, e:
  255. response['message'] = force_unicode(e)
  256. return JsonResponse(response)
  257. @require_POST
  258. def update_permissions(request):
  259. response = {'status': -1, 'message': _('Error')}
  260. data = json.loads(request.POST['data'])
  261. doc_id = request.POST['doc_id']
  262. try:
  263. doc = Document.objects.get_doc_for_writing(doc_id, request.user)
  264. doc.sync_permissions(data)
  265. response['message'] = _('Permissions updated!')
  266. response['status'] = 0
  267. response['doc'] = massage_doc_for_json(doc, request.user)
  268. except KeyError, e:
  269. response['message'] = _('Form is missing %s field') % e.message
  270. except Exception, e:
  271. LOG.exception(e.message)
  272. response['message'] = force_unicode(e)
  273. return JsonResponse(response)