api.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. # -*- coding: utf-8 -*-
  2. """Sample controller with all its actions protected."""
  3. from datetime import datetime
  4. # TODO - D.A. - 2013-11-19
  5. # Check if the new import (ie import io instead of cStringIO)
  6. # is working correctly
  7. #import io as csio
  8. # INFO - D.A. - 2013-11-19
  9. # The PIL import is now taken from the pillow
  10. # which is the python3 port of PIL
  11. #
  12. from PIL import Image as pil
  13. import tg
  14. from tg import expose, flash, require, url, lurl, request, response, redirect, tmpl_context
  15. from tg.i18n import ugettext as _, lazy_ugettext as l_
  16. from tg import predicates as tgp
  17. from tg.i18n import ugettext as _, lazy_ugettext as l_
  18. from pboard.lib.base import BaseController
  19. from pboard.lib import dbapi as pld
  20. from pboard.model import data as pmd
  21. from pboard.model import auth as pma
  22. from pboard.model import serializers as pms
  23. from pboard import model as pm
  24. from pboard.lib.auth import can_read, can_write
  25. from pboard.controllers import apimenu as pcam
  26. FIXME_ERROR_CODE=-1
  27. class PODApiController(BaseController):
  28. """Sample controller-wide authorization"""
  29. allow_only = tgp.in_group('user', msg=l_('You need to login in order to access this ressource'))
  30. menu = pcam.PODApiMenuController()
  31. def on_off_to_boolean(self, on_or_off):
  32. return True if on_or_off=='on' else False
  33. @expose()
  34. def create_event(self, parent_id=None, data_label='', data_datetime=None, data_content='', data_reminder_datetime=None, add_reminder=False, inherit_rights='off', **kw):
  35. loCurrentUser = pld.PODStaticController.getCurrentUser()
  36. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  37. loNewNode = loApiController.createNode(int(parent_id), self.on_off_to_boolean(inherit_rights))
  38. loNewNode.node_type = pmd.PBNodeType.Event
  39. loNewNode.data_label = data_label
  40. loNewNode.data_content = data_content
  41. loNewNode.data_datetime = datetime.strptime(data_datetime, '%d/%m/%Y %H:%M')
  42. if add_reminder:
  43. loNewNode.data_reminder_datetime = data_reminder_datetime
  44. pm.DBSession.flush()
  45. redirect(lurl('/document/%i'%(loNewNode.parent_id)))
  46. @expose()
  47. def create_contact(self, parent_id=None, data_label='', data_content='', inherit_rights='off', **kw):
  48. loCurrentUser = pld.PODStaticController.getCurrentUser()
  49. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  50. loNewNode = loApiController.createNode(int(parent_id), self.on_off_to_boolean(inherit_rights))
  51. loNewNode.node_type = pmd.PBNodeType.Contact
  52. loNewNode.data_label = data_label
  53. loNewNode.data_content = data_content
  54. pm.DBSession.flush()
  55. redirect(lurl('/document/%i'%(loNewNode.parent_id)))
  56. @expose()
  57. def create_comment(self, parent_id=None, data_label='', data_content='', is_shared='', **kw):
  58. loCurrentUser = pld.PODStaticController.getCurrentUser()
  59. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  60. loNewNode = loApiController.createNode(int(parent_id), self.on_off_to_boolean(is_shared))
  61. loNewNode.node_type = pmd.PBNodeType.Comment
  62. loNewNode.data_label = data_label
  63. loNewNode.data_content = data_content
  64. if is_shared=='on':
  65. loNewNode.is_shared = True
  66. pm.DBSession.flush()
  67. redirect(lurl('/document/%i'%(loNewNode.parent_id)))
  68. @expose()
  69. def create_file(self, parent_id=None, data_label='', data_content='', data_file=None, inherit_rights='off', **kw):
  70. loCurrentUser = pld.PODStaticController.getCurrentUser()
  71. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  72. loNewNode = loApiController.createNode(int(parent_id), self.on_off_to_boolean(inherit_rights))
  73. loNewNode.node_type = pmd.PBNodeType.File
  74. loNewNode.data_label = data_label
  75. loNewNode.data_content = data_content
  76. loNewNode.data_file_name = data_file.filename
  77. loNewNode.data_file_mime_type = data_file.type
  78. loNewNode.data_file_content = data_file.file.read()
  79. pm.DBSession.flush()
  80. redirect(lurl('/document/%i'%(loNewNode.parent_id)))
  81. @expose()
  82. @require(can_read())
  83. def get_file_content(self, node_id=None, **kw):
  84. if node_id==None:
  85. return
  86. else:
  87. loCurrentUser = pld.PODStaticController.getCurrentUser()
  88. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  89. loFile = loApiController.getNode(node_id)
  90. lsContentType = "application/x-download"
  91. if loFile.data_file_mime_type!='':
  92. tg.response.headers['Content-type'] = str(loFile.data_file_mime_type)
  93. tg.response.headers['Content-Type'] = lsContentType
  94. tg.response.headers['Content-Disposition'] = str('attachment; filename="%s"'%(loFile.data_file_name))
  95. return loFile.data_file_content
  96. @expose()
  97. def get_file_content_thumbnail(self, node_id=None, **kw):
  98. if node_id==None:
  99. return
  100. else:
  101. loCurrentUser = pld.PODStaticController.getCurrentUser()
  102. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  103. loFile = loApiController.getNode(node_id)
  104. loJpegBytes = csio.StringIO(loFile.data_file_content)
  105. loImage = pil.open(loJpegBytes)
  106. loImage.thumbnail([140,140], pil.ANTIALIAS)
  107. loResultBuffer = csio.StringIO()
  108. loImage.save(loResultBuffer,"JPEG")
  109. tg.response.headers['Content-type'] = str(loFile.data_file_mime_type)
  110. return loResultBuffer.getvalue()
  111. @expose()
  112. @require(can_write())
  113. def set_parent_node(self, node_id, new_parent_id, **kw):
  114. """ @see reindex_nodes() """
  115. loCurrentUser = pld.PODStaticController.getCurrentUser()
  116. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  117. # TODO - D.A. - 2013-11-07 - Check that new parent is accessible by the user !!!
  118. loNewNode = loApiController.getNode(node_id)
  119. if new_parent_id!='':
  120. if new_parent_id==0:
  121. new_parent_id = None
  122. loNewNode.parent_id = int(new_parent_id)
  123. self._updateParentTreePathForNodeAndChildren(loNewNode)
  124. pm.DBSession.flush()
  125. redirect(lurl('/document/%s'%(node_id)))
  126. def _updateParentTreePathForNodeAndChildren(self, moved_node: pmd.PBNode):
  127. """ propagate the move to all child nodes and update there node_depth and parent_tree_path properties """
  128. parent_node = moved_node._oParent
  129. if parent_node==None:
  130. new_parent_tree_path = '/'
  131. moved_node.node_depth = 0
  132. else:
  133. new_parent_tree_path = '{0}{1}/'.format(parent_node.parent_tree_path, parent_node.node_id)
  134. moved_node.node_depth = parent_node.node_depth+1
  135. moved_node.parent_tree_path = new_parent_tree_path
  136. for child_node in moved_node._lAllChildren:
  137. self._updateParentTreePathForNodeAndChildren(child_node)
  138. @expose()
  139. @require(can_write())
  140. def move_node_upper(self, node_id=0):
  141. loCurrentUser = pld.PODStaticController.getCurrentUser()
  142. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  143. loNode = loApiController.getNode(node_id)
  144. if loApiController.moveNodeUpper(loNode)==FIXME_ERROR_CODE:
  145. flash(_('Document #%s can\'t move upper.')%(node_id))
  146. redirect(lurl('/document/%s'%(node_id)))
  147. @expose()
  148. @require(can_write())
  149. def move_node_lower(self, node_id=0):
  150. loCurrentUser = pld.PODStaticController.getCurrentUser()
  151. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  152. loNode = loApiController.getNode(node_id)
  153. if loApiController.moveNodeLower(loNode)==FIXME_ERROR_CODE:
  154. flash(_('Document #%s can\'t move lower.')%(node_id))
  155. redirect(lurl('/document/%s'%(node_id)))
  156. @expose()
  157. def create_document(self, parent_id=None, data_label='', data_content='', inherit_rights='off', node_status=''):
  158. loCurrentUser = pld.PODStaticController.getCurrentUser()
  159. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  160. lsNodeName = 'Document with no name...'
  161. if int(parent_id)!=0:
  162. loParent = loApiController.getNode(parent_id)
  163. lsNodeName = 'Subdocument of (%s)' % loParent.data_label
  164. loNewNode = loApiController.createDummyNode(parent_id, self.on_off_to_boolean(inherit_rights))
  165. loNewNode.data_label = lsNodeName
  166. loNewNode.data_content = 'insert content...'
  167. if data_label!='':
  168. loNewNode.data_label = data_label
  169. if data_content!='':
  170. loNewNode.data_content = data_content
  171. if int(parent_id)!=0:
  172. loNewNode.parent_id = parent_id
  173. if node_status!='':
  174. status_item = pmd.PBNodeStatus.getStatusItem(node_status)
  175. if status_item in pmd.PBNodeStatus.getChoosableList():
  176. loNewNode.node_status = status_item.status_id
  177. pm.DBSession.flush()
  178. redirect(lurl('/document/%i'%(loNewNode.node_id)))
  179. @expose()
  180. @require(can_write())
  181. def edit_status(self, node_id, node_status):
  182. loCurrentUser = pld.PODStaticController.getCurrentUser()
  183. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  184. loNode = loApiController.getNode(node_id)
  185. loNode.node_status = node_status
  186. redirect(lurl('/document/%s'%(node_id)))
  187. @expose()
  188. @require(can_write())
  189. def edit_label_and_content(self, node_id, data_label, data_content):
  190. loCurrentUser = pld.PODStaticController.getCurrentUser()
  191. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  192. loNode = loApiController.getNode(node_id)
  193. loNode.data_label = data_label
  194. loNode.data_content = data_content
  195. redirect(lurl('/document/%s'%(node_id)))
  196. @expose()
  197. @require(can_write())
  198. def force_delete_node(self, node_id=None):
  199. loCurrentUser = pld.PODStaticController.getCurrentUser()
  200. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  201. loNode = loApiController.getNode(node_id)
  202. liParentId = loNode.parent_id
  203. if loNode.getChildNb()<=0:
  204. pm.DBSession.delete(loNode)
  205. flash(_('Document #%s has been deleted')%(node_id))
  206. else:
  207. flash(_('Document #%s can\'t be deleted because it is not empty.')%(node_id), 'error')
  208. redirect(lurl('/document/%s'%(node_id)))
  209. redirect(lurl('/document/%i'%(liParentId or 0)))
  210. def reindex_nodes(self, back_to_node_id=0):
  211. # DA - INFO - 2014-05-26
  212. #
  213. # The following query allows to detect which not are not up-to-date anymore.
  214. # These up-to-date failure is related to the node_depth and parent_tree_path being out-dated.
  215. # This mainly occured when "move node" feature was not working correctly.
  216. #
  217. # The way to fix the data is the following:
  218. # - run mannually the following command
  219. # - for each result, call manually /api/set_parent_node?node_id=parent_node_id&new_parent_id=parent_parent_id
  220. #
  221. sql_query = """
  222. select
  223. pn.node_id as child_node_id,
  224. pn.parent_id as child_parent_id,
  225. pn.parent_tree_path as child_parent_tree_path,
  226. pn.node_depth as child_node_depth,
  227. pnn.node_id as parent_node_id,
  228. pnn.parent_id as parent_parent_id,
  229. pnn.parent_tree_path as parent_parent_tree_path,
  230. pnn.node_depth as parent_node_depth
  231. from
  232. pod_nodes as pn,
  233. pod_nodes as pnn
  234. where
  235. pn.parent_id = pnn.node_id
  236. and pn.parent_tree_path not like pnn.parent_tree_path||'%'
  237. """
  238. return
  239. @expose()
  240. @require(can_write())
  241. def toggle_share_status(self, node_id):
  242. loCurrentUser = pld.PODStaticController.getCurrentUser()
  243. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  244. loNode = loApiController.getNode(node_id)
  245. if loNode.owner_id==loCurrentUser.user_id:
  246. loNode.is_shared = not loNode.is_shared
  247. # FIXME - DA. - 2014-05-06
  248. # - if root node, then exception
  249. # - this redirect is done in order to be adapted to comment share status toggle
  250. redirect(lurl('/document/%s#tab-comments'%(loNode._oParent.node_id)))
  251. @expose()
  252. @require(can_write())
  253. def set_access_management(self, node_id, is_shared='off', read=[0], write=[0]):
  254. llReadAccessGroupIds = [int(liGroupId) for liGroupId in read]
  255. llWriteAccessGroupIds = [int(liGroupId) for liGroupId in write]
  256. # HACK - D.A. - 2015-058-20
  257. # the 0 values are added in order to get a read and write parameters as list even if only one value is inside
  258. # (the default behavior of TG2 is to convert it to a string value if only one value is sent
  259. #
  260. llReadAccessGroupIds.remove(0) # remove useless value
  261. llWriteAccessGroupIds.remove(0) # remove useless value
  262. loCurrentUser = pld.PODStaticController.getCurrentUser()
  263. loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
  264. loNode = loApiController.getNode(node_id)
  265. is_shared_b = False if is_shared=='off' else True
  266. # Only the node owner can modify is_shared
  267. if is_shared_b != loNode.is_shared and loNode.owner_id != loCurrentUser.user_id:
  268. self.back_with_error(_("You can't share a document that doesn't belong to you."))
  269. else:
  270. loNode.is_shared = is_shared_b
  271. if not is_shared_b:
  272. # SHARE IS OFF, so deactivate the document share (and do not change "shared-with" group configuration
  273. pm.DBSession.flush()
  274. redirect(lurl('/document/%s#tab-accessmanagement'%(loNode.node_id)))
  275. # remove all current shares and set the new ones
  276. for loRight in loNode._lRights:
  277. pm.DBSession.delete(loRight)
  278. pm.DBSession.flush()
  279. ldNewRights = dict()
  280. for liGroupId in llReadAccessGroupIds:
  281. ldNewRights[liGroupId] = pma.Rights.READ_ACCESS
  282. for liGroupId in llWriteAccessGroupIds:
  283. liOldValue = 0
  284. if liGroupId in ldNewRights:
  285. liOldValue = ldNewRights[liGroupId]
  286. ldNewRights[liGroupId] = liOldValue + pma.Rights.WRITE_ACCESS
  287. user_list = loApiController._getUserIdListForFiltering()
  288. comments = pm.DBSession.query(pmd.PBNode).filter(pmd.PBNode.parent_id==node_id).\
  289. filter((pmd.PBNode.owner_id.in_(user_list)) | (pma.user_group_table.c.user_id.in_(user_list))).\
  290. filter(pmd.PBNode.node_type=='comment').all()
  291. for comment in comments:
  292. pm.DBSession.add(comment)
  293. for liGroupId, liRightLevel in ldNewRights.items():
  294. loNewRight = loApiController.createRight()
  295. loNewRight.group_id = liGroupId
  296. loNewRight.node_id = node_id
  297. loNewRight.rights = liRightLevel
  298. loNode._lRights.append(loNewRight)
  299. for comment in comments:
  300. comment_right = loApiController.createRight()
  301. comment_right.group_id = liGroupId
  302. comment_right.node_id = comment.node_id
  303. comment_right.rights = liRightLevel
  304. redirect(lurl('/document/%s#tab-accessmanagement'%(loNode.node_id)))