models.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  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 calendar
  18. import logging
  19. import json
  20. import uuid
  21. from itertools import chain
  22. from django.contrib.auth import models as auth_models
  23. from django.contrib.contenttypes import generic
  24. from django.contrib.contenttypes.models import ContentType
  25. from django.contrib.staticfiles.storage import staticfiles_storage
  26. from django.core.urlresolvers import reverse
  27. from django.db import models, transaction
  28. from django.db.models import Q
  29. from django.utils.translation import ugettext as _, ugettext_lazy as _t
  30. from desktop import appmanager
  31. from desktop.lib.i18n import force_unicode
  32. from desktop.lib.exceptions_renderable import PopupException
  33. LOG = logging.getLogger(__name__)
  34. SAMPLE_USERNAME = 'sample'
  35. class UserPreferences(models.Model):
  36. """Holds arbitrary key/value strings."""
  37. user = models.ForeignKey(auth_models.User)
  38. key = models.CharField(max_length=20)
  39. value = models.TextField(max_length=4096)
  40. class Settings(models.Model):
  41. collect_usage = models.BooleanField(db_index=True, default=True)
  42. tours_and_tutorials = models.BooleanField(db_index=True, default=True)
  43. @classmethod
  44. def get_settings(cls):
  45. settings, created = Settings.objects.get_or_create(id=1)
  46. return settings
  47. class DocumentTagManager(models.Manager):
  48. def get_tags(self, user):
  49. return self.filter(owner=user).distinct()
  50. def create_tag(self, owner, tag_name):
  51. if tag_name in DocumentTag.RESERVED:
  52. raise Exception(_("Can't add %s: it is a reserved tag.") % tag_name)
  53. else:
  54. tag, created = DocumentTag.objects.get_or_create(tag=tag_name, owner=owner)
  55. return tag
  56. def _get_tag(self, user, name):
  57. try:
  58. tag, created = DocumentTag.objects.get_or_create(owner=user, tag=name)
  59. except DocumentTag.MultipleObjectsReturned, ex:
  60. # We can delete duplicate tags of a user
  61. dups = DocumentTag.objects.filter(owner=user, tag=name)
  62. tag = dups[0]
  63. for dup in dups[1:]:
  64. LOG.warn('Deleting duplicate %s' % dup)
  65. dup.delete()
  66. return tag
  67. def get_default_tag(self, user):
  68. return self._get_tag(user, DocumentTag.DEFAULT)
  69. def get_trash_tag(self, user):
  70. return self._get_tag(user, DocumentTag.TRASH)
  71. def get_history_tag(self, user):
  72. return self._get_tag(user, DocumentTag.HISTORY)
  73. def get_example_tag(self, user):
  74. return self._get_tag(user, DocumentTag.EXAMPLE)
  75. def tag(self, owner, doc_id, tag_name='', tag_id=None):
  76. try:
  77. tag = DocumentTag.objects.get(id=tag_id, owner=owner)
  78. if tag.tag in DocumentTag.RESERVED:
  79. raise Exception(_("Can't add %s: it is a reserved tag.") % tag)
  80. except DocumentTag.DoesNotExist:
  81. tag = self._get_tag(user=owner, name=tag_name)
  82. doc = Document.objects.get_doc(doc_id, owner)
  83. doc.add_tag(tag)
  84. return tag
  85. def untag(self, tag_id, owner, doc_id):
  86. tag = DocumentTag.objects.get(id=tag_id, owner=owner)
  87. if tag.tag in DocumentTag.RESERVED:
  88. raise Exception(_("Can't remove %s: it is a reserved tag.") % tag)
  89. doc = Document.objects.get_doc(doc_id, owner=owner)
  90. doc.can_write_or_exception(owner)
  91. doc.remove_tag(tag)
  92. def delete_tag(self, tag_id, owner):
  93. tag = DocumentTag.objects.get(id=tag_id, owner=owner)
  94. default_tag = DocumentTag.objects.get_default_tag(owner)
  95. if tag.tag in DocumentTag.RESERVED:
  96. raise Exception(_("Can't remove %s: it is a reserved tag.") % tag)
  97. else:
  98. tag.delete()
  99. for doc in Document.objects.get_docs(owner).filter(tags=None):
  100. doc.add_tag(default_tag)
  101. def update_tags(self, owner, doc_id, tag_ids):
  102. doc = Document.objects.get_doc(doc_id, owner)
  103. doc.can_write_or_exception(owner)
  104. for tag in doc.tags.all():
  105. if tag.tag not in DocumentTag.RESERVED:
  106. doc.remove_tag(tag)
  107. for tag_id in tag_ids:
  108. tag = DocumentTag.objects.get(id=tag_id, owner=owner)
  109. if tag.tag not in DocumentTag.RESERVED:
  110. doc.add_tag(tag)
  111. return doc
  112. class DocumentTag(models.Model):
  113. """
  114. Reserved tags can't be manually removed by the user.
  115. """
  116. owner = models.ForeignKey(auth_models.User, db_index=True)
  117. tag = models.SlugField()
  118. DEFAULT = 'default' # Always there
  119. TRASH = 'trash' # There when the document is trashed
  120. HISTORY = 'history' # There when the document is a submission history
  121. EXAMPLE = 'example' # Hue examples
  122. RESERVED = (DEFAULT, TRASH, HISTORY, EXAMPLE)
  123. objects = DocumentTagManager()
  124. unique_together = ('owner', 'tag')
  125. def __unicode__(self):
  126. return force_unicode('%s') % (self.tag,)
  127. class DocumentManager(models.Manager):
  128. def documents(self, user):
  129. return Document.objects.filter(
  130. Q(owner=user) |
  131. Q(documentpermission__users=user) |
  132. Q(documentpermission__groups__in=user.groups.all())
  133. ).defer('description', 'extra').distinct()
  134. def get_docs(self, user, model_class=None, extra=None):
  135. docs = Document.objects.documents(user).exclude(name='pig-app-hue-script')
  136. if model_class is not None:
  137. ct = ContentType.objects.get_for_model(model_class)
  138. docs = docs.filter(content_type=ct)
  139. if extra is not None:
  140. docs = docs.filter(extra=extra)
  141. return docs
  142. def get_doc(self, doc_id, user):
  143. return Document.objects.documents(user).get(id=doc_id)
  144. def trashed_docs(self, model_class, user):
  145. tag = DocumentTag.objects.get_trash_tag(user=user)
  146. return Document.objects.get_docs(user, model_class).filter(tags__in=[tag]).order_by('-last_modified')
  147. def trashed(self, model_class, user):
  148. docs = self.trashed_docs(model_class, user)
  149. return [job.content_object for job in docs if job.content_object]
  150. def available_docs(self, model_class, user, with_history=False):
  151. exclude = [DocumentTag.objects.get_trash_tag(user=user)]
  152. if not with_history:
  153. exclude.append(DocumentTag.objects.get_history_tag(user=user))
  154. return Document.objects.get_docs(user, model_class).exclude(tags__in=exclude).order_by('-last_modified')
  155. def history_docs(self, model_class, user):
  156. include = [DocumentTag.objects.get_history_tag(user=user)]
  157. exclude = [DocumentTag.objects.get_trash_tag(user=user)]
  158. return Document.objects.get_docs(user, model_class).filter(tags__in=include).exclude(tags__in=exclude).order_by('-last_modified')
  159. def available(self, model_class, user, with_history=False):
  160. docs = self.available_docs(model_class, user, with_history)
  161. return [doc.content_object for doc in docs if doc.content_object]
  162. def can_read_or_exception(self, user, doc_class, doc_id, exception_class=PopupException):
  163. if doc_id is None:
  164. return
  165. try:
  166. ct = ContentType.objects.get_for_model(doc_class)
  167. doc = Document.objects.get(object_id=doc_id, content_type=ct)
  168. if doc.can_read(user):
  169. return doc
  170. else:
  171. message = _("Permission denied. %(username)s does not have the permissions required to access document %(id)s") % \
  172. {'username': user.username, 'id': doc.id}
  173. raise exception_class(message)
  174. except Document.DoesNotExist:
  175. raise exception_class(_('Document %(id)s does not exist') % {'id': doc_id})
  176. def can_read(self, user, doc_class, doc_id):
  177. ct = ContentType.objects.get_for_model(doc_class)
  178. doc = Document.objects.get(object_id=doc_id, content_type=ct)
  179. return doc.can_read(user)
  180. def link(self, content_object, owner, name='', description='', extra=''):
  181. if not content_object.doc.exists():
  182. doc = Document.objects.create(
  183. content_object=content_object,
  184. owner=owner,
  185. name=name,
  186. description=description,
  187. extra=extra
  188. )
  189. tag = DocumentTag.objects.get_default_tag(user=owner)
  190. doc.tags.add(tag)
  191. return doc
  192. else:
  193. LOG.warn('Object %s already has documents: %s' % (content_object, content_object.doc.all()))
  194. return content_object.doc.all()[0]
  195. def sync(self):
  196. try:
  197. with transaction.atomic():
  198. from oozie.models import Workflow, Coordinator, Bundle
  199. for job in list(chain(Workflow.objects.all(), Coordinator.objects.all(), Bundle.objects.all())):
  200. if job.doc.count() > 1:
  201. LOG.warn('Deleting duplicate document %s for %s' % (job.doc.all(), job))
  202. job.doc.all().delete()
  203. if not job.doc.exists():
  204. doc = Document.objects.link(job, owner=job.owner, name=job.name, description=job.description)
  205. tag = DocumentTag.objects.get_example_tag(user=job.owner)
  206. doc.tags.add(tag)
  207. if job.is_trashed:
  208. doc.send_to_trash()
  209. if job.is_shared:
  210. doc.share_to_default()
  211. if hasattr(job, 'managed'):
  212. if not job.managed:
  213. doc.extra = 'jobsub'
  214. doc.save()
  215. if job.owner.username == SAMPLE_USERNAME:
  216. job.doc.get().share_to_default()
  217. except Exception, e:
  218. LOG.warn(force_unicode(e))
  219. try:
  220. with transaction.atomic():
  221. from beeswax.models import SavedQuery
  222. for job in SavedQuery.objects.all():
  223. if job.doc.count() > 1:
  224. LOG.warn('Deleting duplicate document %s for %s' % (job.doc.all(), job))
  225. job.doc.all().delete()
  226. if not job.doc.exists():
  227. doc = Document.objects.link(job, owner=job.owner, name=job.name, description=job.desc, extra=job.type)
  228. tag = DocumentTag.objects.get_example_tag(user=job.owner)
  229. doc.tags.add(tag)
  230. if job.is_trashed:
  231. doc.send_to_trash()
  232. if job.owner.username == SAMPLE_USERNAME:
  233. job.doc.get().share_to_default()
  234. except Exception, e:
  235. LOG.warn(force_unicode(e))
  236. try:
  237. with transaction.atomic():
  238. from pig.models import PigScript
  239. for job in PigScript.objects.all():
  240. if job.doc.count() > 1:
  241. LOG.warn('Deleting duplicate document %s for %s' % (job.doc.all(), job))
  242. job.doc.all().delete()
  243. if not job.doc.exists():
  244. doc = Document.objects.link(job, owner=job.owner, name=job.dict['name'], description='')
  245. tag = DocumentTag.objects.get_example_tag(user=job.owner)
  246. doc.tags.add(tag)
  247. if job.owner.username == SAMPLE_USERNAME:
  248. job.doc.get().share_to_default()
  249. except Exception, e:
  250. LOG.warn(force_unicode(e))
  251. try:
  252. with transaction.atomic():
  253. from search.models import Collection
  254. for dashboard in Collection.objects.all():
  255. col_dict = dashboard.properties_dict['collection']
  256. if not 'uuid' in col_dict:
  257. _uuid = str(uuid.uuid4())
  258. col_dict['uuid'] = _uuid
  259. dashboard.update_properties({'collection': col_dict})
  260. if dashboard.owner is None:
  261. from useradmin.models import install_sample_user
  262. owner = install_sample_user()
  263. else:
  264. owner = dashboard.owner
  265. dashboard_doc = Document2.objects.create(name=dashboard.label, uuid=_uuid, type='search-dashboard', owner=owner, description=dashboard.label, data=dashboard.properties)
  266. Document.objects.link(dashboard_doc, owner=owner, name=dashboard.label, description=dashboard.label, extra='search-dashboard')
  267. dashboard.save()
  268. except Exception, e:
  269. LOG.warn(force_unicode(e))
  270. try:
  271. with transaction.atomic():
  272. for job in Document2.objects.all():
  273. if job.doc.count() > 1:
  274. LOG.warn('Deleting duplicate document %s for %s' % (job.doc.all(), job))
  275. job.doc.all().delete()
  276. if not job.doc.exists():
  277. if job.type == 'oozie-workflow2':
  278. extra = 'workflow2'
  279. elif job.type == 'oozie-coordinator2':
  280. extra = 'coordinator2'
  281. elif job.type == 'oozie-bundle2':
  282. extra = 'bundle2'
  283. elif job.type == 'notebook':
  284. extra = 'notebook'
  285. elif job.type == 'search-dashboard':
  286. extra = 'search-dashboard'
  287. else:
  288. extra = ''
  289. doc = Document.objects.link(job, owner=job.owner, name=job.name, description=job.description, extra=extra)
  290. if job.owner.username == SAMPLE_USERNAME:
  291. doc = job.doc.get()
  292. doc.share_to_default()
  293. tag = DocumentTag.objects.get_example_tag(user=job.owner)
  294. doc.tags.add(tag)
  295. except Exception, e:
  296. LOG.warn(force_unicode(e))
  297. # Make sure doc have at least a tag
  298. try:
  299. for doc in Document.objects.filter(tags=None):
  300. default_tag = DocumentTag.objects.get_default_tag(doc.owner)
  301. doc.tags.add(default_tag)
  302. except Exception, e:
  303. LOG.warn(force_unicode(e))
  304. # For now remove the default tag from the examples
  305. try:
  306. for doc in Document.objects.filter(tags__tag=DocumentTag.EXAMPLE):
  307. default_tag = DocumentTag.objects.get_default_tag(doc.owner)
  308. doc.tags.remove(default_tag)
  309. except Exception, e:
  310. LOG.warn(force_unicode(e))
  311. # Delete documents with no object
  312. try:
  313. for doc in Document.objects.all():
  314. if doc.content_type is None or doc.content_object is None:
  315. doc.delete()
  316. except Exception, e:
  317. LOG.warn(force_unicode(e))
  318. UTC_TIME_FORMAT = "%Y-%m-%dT%H:%MZ"
  319. class Document(models.Model):
  320. owner = models.ForeignKey(auth_models.User, db_index=True, verbose_name=_t('Owner'), help_text=_t('User who can own the job.'), related_name='doc_owner')
  321. name = models.CharField(default='', max_length=255)
  322. description = models.TextField(default='')
  323. last_modified = models.DateTimeField(auto_now=True, db_index=True, verbose_name=_t('Last modified'))
  324. version = models.SmallIntegerField(default=1, verbose_name=_t('Schema version'))
  325. extra = models.TextField(default='')
  326. tags = models.ManyToManyField(DocumentTag, db_index=True)
  327. content_type = models.ForeignKey(ContentType)
  328. object_id = models.PositiveIntegerField()
  329. content_object = generic.GenericForeignKey('content_type', 'object_id')
  330. objects = DocumentManager()
  331. unique_together = ('content_type', 'object_id')
  332. def __unicode__(self):
  333. return force_unicode('%s %s %s') % (self.content_type, self.name, self.owner)
  334. def is_editable(self, user):
  335. """Deprecated by can_read"""
  336. return self.can_write(user)
  337. def can_edit_or_exception(self, user, exception_class=PopupException):
  338. """Deprecated by can_write_or_exception"""
  339. return self.can_write_or_exception(user, exception_class)
  340. def add_tag(self, tag):
  341. self.tags.add(tag)
  342. def remove_tag(self, tag):
  343. self.tags.remove(tag)
  344. def is_trashed(self):
  345. return DocumentTag.objects.get_trash_tag(user=self.owner) in self.tags.all()
  346. def is_historic(self):
  347. return DocumentTag.objects.get_history_tag(user=self.owner) in self.tags.all()
  348. def send_to_trash(self):
  349. tag = DocumentTag.objects.get_trash_tag(user=self.owner)
  350. self.tags.add(tag)
  351. def restore_from_trash(self):
  352. tag = DocumentTag.objects.get_trash_tag(user=self.owner)
  353. self.tags.remove(tag)
  354. def add_to_history(self):
  355. tag = DocumentTag.objects.get_history_tag(user=self.owner)
  356. self.tags.add(tag)
  357. def remove_from_history(self):
  358. tag = DocumentTag.objects.get_history_tag(user=self.owner)
  359. self.tags.remove(tag)
  360. def share_to_default(self, name='read'):
  361. DocumentPermission.objects.share_to_default(self, name=name)
  362. def can_read(self, user):
  363. return user.is_superuser or self.owner == user or Document.objects.get_docs(user).filter(id=self.id).exists()
  364. def can_write(self, user):
  365. perm = self.list_permissions('write')
  366. return user.is_superuser or self.owner == user or perm.groups.filter(id__in=user.groups.all()).exists() or user in perm.users.all()
  367. def can_read_or_exception(self, user, exception_class=PopupException):
  368. if self.can_read(user):
  369. return True
  370. else:
  371. raise exception_class(_('Only superusers and %s are allowed to read this document.') % user)
  372. def can_write_or_exception(self, user, exception_class=PopupException):
  373. if self.can_write(user):
  374. return True
  375. else:
  376. raise exception_class(_('Only superusers and %s are allowed to write this document.') % user)
  377. def copy(self, name=None, owner=None):
  378. copy_doc = self
  379. copy_doc.pk = None
  380. copy_doc.id = None
  381. if name is not None:
  382. copy_doc.name = name
  383. if owner is not None:
  384. copy_doc.owner = owner
  385. copy_doc.save()
  386. # Don't copy tags
  387. default_tag = DocumentTag.objects.get_default_tag(copy_doc.owner)
  388. tags = [default_tag]
  389. copy_doc.tags.add(*tags)
  390. return copy_doc
  391. @property
  392. def icon(self):
  393. apps = appmanager.get_apps_dict()
  394. try:
  395. if self.extra == 'workflow2':
  396. return staticfiles_storage.url('oozie/art/icon_oozie_workflow_48.png')
  397. elif self.extra == 'coordinator2':
  398. return staticfiles_storage.url('oozie/art/icon_oozie_coordinator_48.png')
  399. elif self.extra == 'bundle2':
  400. return staticfiles_storage.url('oozie/art/icon_oozie_bundle_48.png')
  401. elif self.extra == 'notebook':
  402. return staticfiles_storage.url('spark/art/icon_spark_48.png')
  403. elif self.extra.startswith('search'):
  404. return staticfiles_storage.url('search/art/icon_search_48.png')
  405. elif self.content_type.app_label == 'beeswax':
  406. if self.extra == '0':
  407. return staticfiles_storage.url(apps['beeswax'].icon_path)
  408. elif self.extra == '3':
  409. return staticfiles_storage.url(apps['spark'].icon_path)
  410. else:
  411. return staticfiles_storage.url(apps['impala'].icon_path)
  412. elif self.content_type.app_label == 'oozie':
  413. if self.extra == 'jobsub':
  414. return staticfiles_storage.url(apps['jobsub'].icon_path)
  415. else:
  416. return staticfiles_storage.url(self.content_type.model_class().ICON)
  417. elif self.content_type.app_label in apps:
  418. return staticfiles_storage.url(apps[self.content_type.app_label].icon_path)
  419. else:
  420. return staticfiles_storage.url('desktop/art/icon_hue_48.png')
  421. except Exception, e:
  422. LOG.warn(force_unicode(e))
  423. return staticfiles_storage.url('desktop/art/icon_hue_48.png')
  424. def share(self, users, groups, name='read'):
  425. DocumentPermission.objects.filter(document=self, name=name).update(users=users, groups=groups, add=True)
  426. def unshare(self, users, groups, name='read'):
  427. DocumentPermission.objects.filter(document=self, name=name).update(users=users, groups=groups, add=False)
  428. def sync_permissions(self, perms_dict):
  429. """
  430. Set who else or which other group can interact with the document.
  431. Example of input: {'read': {'user_ids': [1, 2, 3], 'group_ids': [1, 2, 3]}}
  432. """
  433. for name, perm in perms_dict.iteritems():
  434. users = groups = None
  435. if perm.get('user_ids'):
  436. users = auth_models.User.objects.in_bulk(perm.get('user_ids'))
  437. if perm.get('group_ids'):
  438. groups = auth_models.Group.objects.in_bulk(perm.get('group_ids'))
  439. else:
  440. groups = []
  441. DocumentPermission.objects.sync(document=self, name=name, users=users, groups=groups)
  442. def list_permissions(self, perm='read'):
  443. return DocumentPermission.objects.list(document=self, perm=perm)
  444. def to_dict(self):
  445. return {
  446. 'owner': self.owner.username,
  447. 'name': self.name,
  448. 'description': self.description,
  449. 'uuid': None, # no uuid == v1
  450. 'id': self.id,
  451. 'doc1_id': self.id,
  452. 'object_id': self.object_id,
  453. 'type': str(self.content_type),
  454. 'last_modified': self.last_modified.strftime(UTC_TIME_FORMAT),
  455. 'last_modified_ts': calendar.timegm(self.last_modified.utctimetuple()),
  456. 'isSelected': False
  457. }
  458. class DocumentPermissionManager(models.Manager):
  459. def _check_perm(self, name):
  460. perms = (DocumentPermission.READ_PERM, DocumentPermission.WRITE_PERM)
  461. if name not in perms:
  462. perms_string = ' and '.join(', '.join(perms).rsplit(', ', 1))
  463. raise PopupException(_('Only %s permissions are supported, not %s.') % (perms_string, name))
  464. def share_to_default(self, document, name='read'):
  465. from useradmin.models import get_default_user_group # Remove build dependency
  466. self._check_perm(name)
  467. if name == DocumentPermission.WRITE_PERM:
  468. perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=DocumentPermission.WRITE_PERM)
  469. else:
  470. perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=DocumentPermission.READ_PERM)
  471. default_group = get_default_user_group()
  472. if default_group:
  473. perm.groups.add(default_group)
  474. def update(self, document, name='read', users=None, groups=None, add=True):
  475. self._check_perm(name)
  476. perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=name)
  477. if users is not None:
  478. if add:
  479. perm.users.add(*users)
  480. else:
  481. perm.users.remove(*users)
  482. if groups is not None:
  483. if add:
  484. perm.groups.add(*groups)
  485. else:
  486. perm.groups.remove(*groups)
  487. if not perm.users and not perm.groups:
  488. perm.delete()
  489. def sync(self, document, name='read', users=None, groups=None):
  490. self._check_perm(name)
  491. perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=name)
  492. if users is not None:
  493. perm.users = []
  494. perm.users = users
  495. perm.save()
  496. if groups is not None:
  497. perm.groups = []
  498. perm.groups = groups
  499. perm.save()
  500. if not users and not groups:
  501. perm.delete()
  502. def list(self, document, perm='read'):
  503. try:
  504. perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=perm)
  505. except DocumentPermission.MultipleObjectsReturned:
  506. # We can delete duplicate perms of a document
  507. dups = DocumentPermission.objects.filter(doc=document, perms=perm)
  508. perm = dups[0]
  509. for dup in dups[1:]:
  510. LOG.warn('Deleting duplicate %s' % dup)
  511. dup.delete()
  512. return perm
  513. class DocumentPermission(models.Model):
  514. READ_PERM = 'read'
  515. WRITE_PERM = 'write'
  516. doc = models.ForeignKey(Document)
  517. users = models.ManyToManyField(auth_models.User, db_index=True, db_table='documentpermission_users')
  518. groups = models.ManyToManyField(auth_models.Group, db_index=True, db_table='documentpermission_groups')
  519. perms = models.TextField(default=READ_PERM, choices=( # one perm
  520. (READ_PERM, 'read'),
  521. (WRITE_PERM, 'write'),
  522. ))
  523. objects = DocumentPermissionManager()
  524. unique_together = ('doc', 'perms')
  525. class Document2Manager(models.Manager):
  526. def get_by_natural_key(self, uuid, version, is_history):
  527. return self.get(uuid=uuid, version=version, is_history=is_history)
  528. def uuid_default():
  529. return str(uuid.uuid4())
  530. class Document2(models.Model):
  531. owner = models.ForeignKey(auth_models.User, db_index=True, verbose_name=_t('Owner'), help_text=_t('Creator.'), related_name='doc2_owner')
  532. name = models.CharField(default='', max_length=255)
  533. description = models.TextField(default='')
  534. uuid = models.CharField(default=uuid_default, max_length=36, db_index=True)
  535. type = models.CharField(default='', max_length=32, db_index=True, help_text=_t('Type of document, e.g. Hive query, Oozie workflow, Search Dashboard...'))
  536. data = models.TextField(default='{}')
  537. extra = models.TextField(default='')
  538. last_modified = models.DateTimeField(auto_now=True, db_index=True, verbose_name=_t('Time last modified'))
  539. version = models.SmallIntegerField(default=1, verbose_name=_t('Document version'), db_index=True)
  540. is_history = models.BooleanField(default=False, db_index=True)
  541. tags = models.ManyToManyField('self', db_index=True)
  542. dependencies = models.ManyToManyField('self', db_index=True)
  543. doc = generic.GenericRelation(Document, related_name='doc_doc') # Compatibility with Hue 3
  544. objects = Document2Manager()
  545. unique_together = ('uuid', 'version', 'is_history')
  546. def natural_key(self):
  547. return (self.uuid, self.version, self.is_history)
  548. @property
  549. def data_dict(self):
  550. if not self.data:
  551. self.data = json.dumps({})
  552. data_python = json.loads(self.data)
  553. return data_python
  554. def update_data(self, post_data):
  555. data_dict = self.data_dict
  556. data_dict.update(post_data)
  557. self.data = json.dumps(data_dict)
  558. def get_absolute_url(self):
  559. if self.type == 'oozie-coordinator2':
  560. return reverse('oozie:edit_coordinator') + '?coordinator=' + str(self.id)
  561. elif self.type == 'oozie-bundle2':
  562. return reverse('oozie:edit_bundle') + '?bundle=' + str(self.id)
  563. elif self.type == 'notebook':
  564. return reverse('spark:editor') + '?notebook=' + str(self.id)
  565. elif self.type == 'search-dashboard':
  566. return reverse('search:index') + '?collection=' + str(self.id)
  567. else:
  568. return reverse('oozie:edit_workflow') + '?workflow=' + str(self.id)
  569. def to_dict(self):
  570. return {
  571. 'owner': self.owner.username,
  572. 'name': self.name,
  573. 'description': self.description,
  574. 'uuid': self.uuid,
  575. 'id': self.id,
  576. 'doc1_id': self.doc.get().id if self.doc.exists() else -1,
  577. 'type': self.type,
  578. 'last_modified': self.last_modified.strftime(UTC_TIME_FORMAT),
  579. 'last_modified_ts': calendar.timegm(self.last_modified.utctimetuple()),
  580. 'isSelected': False
  581. }
  582. def can_read_or_exception(self, user):
  583. self.doc.get().can_read_or_exception(user)