models.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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. from itertools import chain
  19. from django.db import models
  20. from django.db.models import Q
  21. from django.contrib.auth import models as auth_models
  22. from django.contrib.contenttypes.models import ContentType
  23. from django.contrib.contenttypes import generic
  24. from django.utils.translation import ugettext as _, ugettext_lazy as _t
  25. from desktop.lib.i18n import force_unicode
  26. from desktop.lib.exceptions_renderable import PopupException
  27. from desktop import appmanager
  28. LOG = logging.getLogger(__name__)
  29. SAMPLE_USERNAME = 'sample'
  30. class UserPreferences(models.Model):
  31. """Holds arbitrary key/value strings."""
  32. user = models.ForeignKey(auth_models.User)
  33. key = models.CharField(max_length=20)
  34. value = models.TextField(max_length=4096)
  35. class Settings(models.Model):
  36. collect_usage = models.BooleanField(db_index=True, default=True)
  37. tours_and_tutorials = models.BooleanField(db_index=True, default=True)
  38. @classmethod
  39. def get_settings(cls):
  40. settings, created = Settings.objects.get_or_create(id=1)
  41. return settings
  42. class DocumentTagManager(models.Manager):
  43. def get_tags(self, user):
  44. # For now, the only shared tag is from 'sample' user and is named 'example'
  45. # Tag permissions will come later.
  46. # Share Tag from shared document will come later.
  47. tags = self
  48. try:
  49. sample_user = auth_models.User.objects.get(username=SAMPLE_USERNAME)
  50. tags = tags.filter(Q(owner=user) | Q(owner=sample_user, tag=DocumentTag.EXAMPLE))
  51. except:
  52. tags = tags.filter(owner=user)
  53. return tags.distinct()
  54. def create_tag(self, owner, tag_name):
  55. if tag_name in DocumentTag.RESERVED:
  56. raise Exception(_("Can't add %s: it is a reserved tag.") % tag_name)
  57. else:
  58. tag, created = DocumentTag.objects.get_or_create(tag=tag_name, owner=owner)
  59. return tag
  60. def _get_tag(self, user, name):
  61. try:
  62. tag, created = DocumentTag.objects.get_or_create(owner=user, tag=name)
  63. except DocumentTag.MultipleObjectsReturned, ex:
  64. # We can delete duplicate tags of a user
  65. dups = DocumentTag.objects.filter(owner=user, tag=name)
  66. tag = dups[0]
  67. for dup in dups[1:]:
  68. LOG.warn('Deleting duplicate %s' % dup)
  69. dup.delete()
  70. return tag
  71. def get_default_tag(self, user):
  72. return self._get_tag(user, DocumentTag.DEFAULT)
  73. def get_trash_tag(self, user):
  74. return self._get_tag(user, DocumentTag.TRASH)
  75. def get_history_tag(self, user):
  76. return self._get_tag(user, DocumentTag.HISTORY)
  77. def get_example_tag(self, user):
  78. return self._get_tag(user, DocumentTag.EXAMPLE)
  79. def tag(self, owner, doc_id, tag_name='', tag_id=None):
  80. try:
  81. tag = DocumentTag.objects.get(id=tag_id, owner=owner)
  82. if tag.tag in DocumentTag.RESERVED:
  83. raise Exception(_("Can't add %s: it is a reserved tag.") % tag)
  84. except DocumentTag.DoesNotExist:
  85. tag = self._get_tag(user=owner, name=tag_name)
  86. doc = Document.objects.get_doc(doc_id, owner)
  87. doc.add_tag(tag)
  88. return tag
  89. def untag(self, tag_id, owner, doc_id):
  90. tag = DocumentTag.objects.get(id=tag_id, owner=owner)
  91. if tag.tag in DocumentTag.RESERVED:
  92. raise Exception(_("Can't remove %s: it is a reserved tag.") % tag)
  93. doc = Document.objects.get_doc(doc_id, owner=owner)
  94. doc.remove_tag(tag)
  95. def delete_tag(self, tag_id, owner):
  96. tag = DocumentTag.objects.get(id=tag_id, owner=owner)
  97. default_tag = DocumentTag.objects.get_default_tag(owner)
  98. if tag.tag in DocumentTag.RESERVED:
  99. raise Exception(_("Can't remove %s: it is a reserved tag.") % tag)
  100. else:
  101. tag.delete()
  102. for doc in Document.objects.get_docs(owner).filter(tags=None):
  103. doc.add_tag(default_tag)
  104. def update_tags(self, owner, doc_id, tag_ids):
  105. # TODO secu
  106. doc = Document.objects.get_doc(doc_id, owner)
  107. for tag in doc.tags.all():
  108. if tag.tag not in DocumentTag.RESERVED:
  109. doc.remove_tag(tag)
  110. for tag_id in tag_ids:
  111. tag = DocumentTag.objects.get(id=tag_id, owner=owner)
  112. if tag.tag not in DocumentTag.RESERVED:
  113. doc.add_tag(tag)
  114. return doc
  115. class DocumentTag(models.Model):
  116. """
  117. Reserved tags can't be manually removed by the user.
  118. """
  119. owner = models.ForeignKey(auth_models.User, db_index=True)
  120. tag = models.SlugField()
  121. DEFAULT = 'default' # Always there
  122. TRASH = 'trash' # There when the document is trashed
  123. HISTORY = 'history' # There when the document is a submission history
  124. EXAMPLE = 'example' # Hue examples
  125. RESERVED = (DEFAULT, TRASH, HISTORY, EXAMPLE)
  126. objects = DocumentTagManager()
  127. unique_together = ('owner', 'tag')
  128. def __unicode__(self):
  129. return force_unicode('%s') % (self.tag,)
  130. class DocumentManager(models.Manager):
  131. def documents(self, user):
  132. return Document.objects.filter(Q(owner=user) | Q(documentpermission__users=user) | Q(documentpermission__groups__in=user.groups.all())).distinct()
  133. def get_docs(self, user, model_class=None, extra=None):
  134. docs = Document.objects.documents(user).exclude(name='pig-app-hue-script')
  135. if model_class is not None:
  136. ct = ContentType.objects.get_for_model(model_class)
  137. docs = docs.filter(content_type=ct)
  138. if extra is not None:
  139. docs = docs.filter(extra=extra)
  140. return docs
  141. def get_doc(self, doc_id, user):
  142. return Document.objects.documents(user).get(id=doc_id)
  143. def trashed_docs(self, model_class, user):
  144. tag = DocumentTag.objects.get_trash_tag(user=user)
  145. return Document.objects.get_docs(user, model_class).filter(tags__in=[tag]).order_by('-last_modified')
  146. def trashed(self, model_class, user):
  147. docs = self.trashed_docs(model_class, user)
  148. return [job.content_object for job in docs if job.content_object]
  149. def available_docs(self, model_class, user):
  150. trash = DocumentTag.objects.get_trash_tag(user=user)
  151. history = DocumentTag.objects.get_history_tag(user=user)
  152. return Document.objects.get_docs(user, model_class).exclude(tags__in=[trash, history]).order_by('-last_modified')
  153. def available(self, model_class, user):
  154. docs = self.available_docs(model_class, user)
  155. return [doc.content_object for doc in docs if doc.content_object]
  156. def can_read_or_exception(self, user, doc_class, doc_id, exception_class=PopupException):
  157. if doc_id is None:
  158. return
  159. try:
  160. ct = ContentType.objects.get_for_model(doc_class)
  161. doc = Document.objects.get(object_id=doc_id, content_type=ct)
  162. if doc.can_read(user):
  163. return doc
  164. else:
  165. message = _("Permission denied. %(username)s does not have the permissions required to access document %(id)s") % \
  166. {'username': user.username, 'id': doc.id}
  167. raise exception_class(message)
  168. except Document.DoesNotExist:
  169. raise exception_class(_('Document %(id)s does not exist') % {'id': doc_id})
  170. def can_read(self, user, doc_class, doc_id):
  171. ct = ContentType.objects.get_for_model(doc_class)
  172. doc = Document.objects.get(object_id=doc_id, content_type=ct)
  173. return doc.can_read(user)
  174. def link(self, content_object, owner, name='', description='', extra=''):
  175. if not content_object.doc.exists():
  176. doc = Document.objects.create(
  177. content_object=content_object,
  178. owner=owner,
  179. name=name,
  180. description=description,
  181. extra=extra
  182. )
  183. tag = DocumentTag.objects.get_default_tag(user=owner)
  184. doc.tags.add(tag)
  185. return doc
  186. else:
  187. LOG.warn('Object %s already has documents: %s' % (content_object, content_object.doc.all()))
  188. return content_object.doc.all()[0]
  189. def sync(self):
  190. try:
  191. from oozie.models import Workflow, Coordinator, Bundle
  192. for job in list(chain(Workflow.objects.all(), Coordinator.objects.all(), Bundle.objects.all())):
  193. if job.doc.count() > 1:
  194. LOG.warn('Deleting duplicate document %s for %s' % (job.doc.all(), job))
  195. job.doc.all().delete()
  196. if not job.doc.exists():
  197. doc = Document.objects.link(job, owner=job.owner, name=job.name, description=job.description)
  198. tag = DocumentTag.objects.get_example_tag(user=job.owner)
  199. doc.tags.add(tag)
  200. if job.is_trashed:
  201. doc.send_to_trash()
  202. if job.is_shared:
  203. doc.share_to_default()
  204. if hasattr(job, 'managed'):
  205. if not job.managed:
  206. doc.extra = 'jobsub'
  207. doc.save()
  208. if job.owner.username == SAMPLE_USERNAME:
  209. job.doc.get().share_to_default()
  210. except Exception, e:
  211. LOG.warn(force_unicode(e))
  212. try:
  213. from beeswax.models import SavedQuery
  214. for job in SavedQuery.objects.all():
  215. if job.doc.count() > 1:
  216. LOG.warn('Deleting duplicate document %s for %s' % (job.doc.all(), job))
  217. job.doc.all().delete()
  218. if not job.doc.exists():
  219. doc = Document.objects.link(job, owner=job.owner, name=job.name, description=job.desc, extra=job.type)
  220. tag = DocumentTag.objects.get_example_tag(user=job.owner)
  221. doc.tags.add(tag)
  222. if job.is_trashed:
  223. doc.send_to_trash()
  224. if job.owner.username == SAMPLE_USERNAME:
  225. job.doc.get().share_to_default()
  226. except Exception, e:
  227. LOG.warn(force_unicode(e))
  228. try:
  229. from pig.models import PigScript
  230. for job in PigScript.objects.all():
  231. if job.doc.count() > 1:
  232. LOG.warn('Deleting duplicate document %s for %s' % (job.doc.all(), job))
  233. job.doc.all().delete()
  234. if not job.doc.exists():
  235. doc = Document.objects.link(job, owner=job.owner, name=job.dict['name'], description='')
  236. tag = DocumentTag.objects.get_example_tag(user=job.owner)
  237. doc.tags.add(tag)
  238. if job.owner.username == SAMPLE_USERNAME:
  239. job.doc.get().share_to_default()
  240. except Exception, e:
  241. LOG.warn(force_unicode(e))
  242. # Make sure doc have at least a tag
  243. try:
  244. for doc in Document.objects.filter(tags=None):
  245. default_tag = DocumentTag.objects.get_default_tag(doc.owner)
  246. doc.tags.add(default_tag)
  247. except Exception, e:
  248. LOG.warn(force_unicode(e))
  249. # For now remove the default tag from the examples
  250. try:
  251. for doc in Document.objects.filter(tags__tag=DocumentTag.EXAMPLE):
  252. default_tag = DocumentTag.objects.get_default_tag(doc.owner)
  253. doc.tags.remove(default_tag)
  254. except Exception, e:
  255. LOG.warn(force_unicode(e))
  256. # Delete documents with no object
  257. try:
  258. for doc in Document.objects.all():
  259. if doc.content_type is None:
  260. doc.delete()
  261. except Exception, e:
  262. LOG.warn(force_unicode(e))
  263. class Document(models.Model):
  264. 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')
  265. name = models.TextField(default='')
  266. description = models.TextField(default='')
  267. last_modified = models.DateTimeField(auto_now=True, db_index=True, verbose_name=_t('Last modified'))
  268. version = models.SmallIntegerField(default=1, verbose_name=_t('Schema version'))
  269. extra = models.TextField(default='')
  270. tags = models.ManyToManyField(DocumentTag, db_index=True)
  271. content_type = models.ForeignKey(ContentType)
  272. object_id = models.PositiveIntegerField()
  273. content_object = generic.GenericForeignKey('content_type', 'object_id')
  274. objects = DocumentManager()
  275. unique_together = ('content_type', 'object_id')
  276. def __unicode__(self):
  277. return force_unicode('%s %s %s') % (self.content_type, self.name, self.owner)
  278. def is_editable(self, user):
  279. """Deprecated by can_read"""
  280. return self.can_write(user)
  281. def can_edit_or_exception(self, user, exception_class=PopupException):
  282. """Deprecated by can_write_or_exception"""
  283. return self.can_write_or_exception(user, exception_class)
  284. def add_tag(self, tag):
  285. self.tags.add(tag)
  286. def remove_tag(self, tag):
  287. self.tags.remove(tag)
  288. def is_trashed(self):
  289. return DocumentTag.objects.get_trash_tag(user=self.owner) in self.tags.all()
  290. def is_historic(self):
  291. return DocumentTag.objects.get_history_tag(user=self.owner) in self.tags.all()
  292. def send_to_trash(self):
  293. tag = DocumentTag.objects.get_trash_tag(user=self.owner)
  294. self.tags.add(tag)
  295. def restore_from_trash(self):
  296. tag = DocumentTag.objects.get_trash_tag(user=self.owner)
  297. self.tags.remove(tag)
  298. def add_to_history(self):
  299. tag = DocumentTag.objects.get_history_tag(user=self.owner)
  300. self.tags.add(tag)
  301. default_tag = DocumentTag.objects.get_default_tag(user=self.owner)
  302. self.tags.remove(default_tag)
  303. def share_to_default(self):
  304. DocumentPermission.objects.share_to_default(self)
  305. def can_read(self, user):
  306. return user.is_superuser or self.owner == user or Document.objects.get_docs(user).filter(id=self.id).exists()
  307. def can_write(self, user):
  308. return user.is_superuser or self.owner == user
  309. def can_read_or_exception(self, user, exception_class=PopupException):
  310. if self.can_read(user):
  311. return True
  312. else:
  313. raise exception_class(_('Only superusers and %s are allowed to read this document.') % user)
  314. def can_write_or_exception(self, user, exception_class=PopupException):
  315. if self.can_write(user):
  316. return True
  317. else:
  318. raise exception_class(_('Only superusers and %s are allowed to modify this document.') % user)
  319. def copy(self, name=None, owner=None):
  320. copy_doc = self
  321. tags = self.tags.all()
  322. copy_doc.pk = None
  323. copy_doc.id = None
  324. if name is not None:
  325. copy_doc.name = name
  326. if owner is not None:
  327. copy_doc.owner = owner
  328. copy_doc.save()
  329. tags = filter(lambda tag: tag.tag != DocumentTag.EXAMPLE, tags)
  330. if not tags:
  331. default_tag = DocumentTag.objects.get_default_tag(copy_doc.owner)
  332. tags = [default_tag]
  333. copy_doc.tags.add(*tags)
  334. return copy_doc
  335. @property
  336. def icon(self):
  337. apps = appmanager.get_apps_dict()
  338. try:
  339. if self.content_type.app_label == 'beeswax':
  340. if self.extra == '0':
  341. return apps['beeswax'].icon_path
  342. elif self.extra == '3':
  343. return apps['spark'].icon_path
  344. else:
  345. return apps['impala'].icon_path
  346. elif self.content_type.app_label == 'oozie':
  347. if self.extra == 'jobsub':
  348. return apps['jobsub'].icon_path
  349. else:
  350. return self.content_type.model_class().ICON
  351. elif self.content_type.app_label in apps:
  352. return apps[self.content_type.app_label].icon_path
  353. else:
  354. return '/static/art/favicon.png'
  355. except Exception, e:
  356. LOG.warn(force_unicode(e))
  357. return '/static/art/favicon.png'
  358. def share(self, users, groups, name='read'):
  359. DocumentPermission.objects.filter(document=self, name=name).update(users=users, groups=groups, add=True)
  360. def unshare(self, users, groups, name='read'):
  361. DocumentPermission.objects.filter(document=self, name=name).update(users=users, groups=groups, add=False)
  362. def sync_permissions(self, perms_dict):
  363. """
  364. Set who else or which other group can interact with the document.
  365. Example of input: {'read': {'user_ids': [1, 2, 3], 'group_ids': [1, 2, 3]}}
  366. """
  367. for name, perm in perms_dict.iteritems():
  368. users = groups = None
  369. if perm.get('user_ids'):
  370. users = auth_models.User.objects.in_bulk(perm.get('user_ids'))
  371. if perm.get('group_ids'):
  372. groups = auth_models.Group.objects.in_bulk(perm.get('group_ids'))
  373. DocumentPermission.objects.sync(document=self, name=name, users=users, groups=groups)
  374. def list_permissions(self):
  375. return DocumentPermission.objects.list(document=self)
  376. class DocumentPermissionManager(models.Manager):
  377. def share_to_default(self, document):
  378. from useradmin.models import get_default_user_group # Remove build dependency
  379. perm, created = DocumentPermission.objects.get_or_create(doc=document)
  380. default_group = get_default_user_group()
  381. if default_group:
  382. perm.groups.add(default_group)
  383. def update(self, document, name='read', users=None, groups=None, add=True):
  384. if name != DocumentPermission.READ_PERM:
  385. raise PopupException(_('Only %s permissions is supported, not %s.') % (DocumentPermission.READ_PERM, name))
  386. perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=name)
  387. if users is not None:
  388. if add:
  389. perm.users.add(*users)
  390. else:
  391. perm.users.remove(*users)
  392. if groups is not None:
  393. if add:
  394. perm.groups.add(*groups)
  395. else:
  396. perm.groups.remove(*groups)
  397. if not perm.users and not perm.groups:
  398. perm.delete()
  399. def sync(self, document, name='read', users=None, groups=None):
  400. if name != DocumentPermission.READ_PERM:
  401. raise PopupException(_('Only %s permissions is supported, not %s.') % (DocumentPermission.READ_PERM, name))
  402. perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=name)
  403. if users is not None:
  404. perm.users = []
  405. perm.users = users
  406. perm.save()
  407. if groups is not None:
  408. perm.groups = []
  409. perm.groups = groups
  410. perm.save()
  411. if not users and not groups:
  412. perm.delete()
  413. def list(self, document):
  414. try:
  415. perm, created = DocumentPermission.objects.get_or_create(doc=document, perms=DocumentPermission.READ_PERM)
  416. except DocumentPermission.MultipleObjectsReturned, ex:
  417. # We can delete duplicate perms of a document
  418. dups = DocumentPermission.objects.filter(doc=document, perms=DocumentPermission.READ_PERM)
  419. perm = dups[0]
  420. for dup in dups[1:]:
  421. LOG.warn('Deleting duplicate %s' % dup)
  422. dup.delete()
  423. return perm
  424. class DocumentPermission(models.Model):
  425. READ_PERM = 'read'
  426. doc = models.ForeignKey(Document)
  427. users = models.ManyToManyField(auth_models.User, db_index=True)
  428. groups = models.ManyToManyField(auth_models.Group, db_index=True)
  429. perms = models.TextField(default=READ_PERM, choices=((READ_PERM, 'read'),))
  430. objects = DocumentPermissionManager()
  431. unique_together = ('doc', 'perms')
  432. # HistoryTable
  433. # VersionTable