workspace.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. # -*- coding: utf-8 -*-
  2. import tg
  3. from tg import tmpl_context
  4. from tg.i18n import ugettext as _
  5. from tg.predicates import not_anonymous
  6. from tracim.config.app_cfg import CFG
  7. from tracim.controllers import TIMRestController
  8. from tracim.controllers.content import UserWorkspaceFolderRestController
  9. from tracim.controllers.jitsi_meet import JitsiMeetController
  10. from tracim.lib.helpers import convert_id_into_instances
  11. from tracim.lib.content import ContentApi
  12. from tracim.lib.utils import str_as_bool
  13. from tracim.lib.workspace import WorkspaceApi
  14. from tracim.model.data import NodeTreeItem
  15. from tracim.model.data import Content
  16. from tracim.model.data import ContentType
  17. from tracim.model.data import Workspace
  18. from tracim.model.serializers import Context, CTX, DictLikeClass
  19. from urllib.parse import urlparse
  20. class UserWorkspaceRestController(TIMRestController):
  21. allow_only = not_anonymous()
  22. folders = UserWorkspaceFolderRestController()
  23. videoconference = JitsiMeetController()
  24. @property
  25. def _base_url(self):
  26. return '/dashboard/workspaces'
  27. @classmethod
  28. def current_item_id_key_in_context(cls) -> str:
  29. return 'workspace_id'
  30. @tg.expose()
  31. def get_all(self, *args, **kw):
  32. tg.redirect(tg.url('/home'))
  33. @tg.expose()
  34. def mark_read(self, workspace_id, **kwargs):
  35. user = tmpl_context.current_user
  36. workspace_api = WorkspaceApi(user)
  37. workspace = workspace_api.get_one(workspace_id)
  38. content_api = ContentApi(user)
  39. content_api.mark_read__workspace(workspace)
  40. tg.redirect('/workspaces/{}'.format(workspace_id))
  41. return DictLikeClass(fake_api=fake_api)
  42. @tg.expose('tracim.templates.workspace.getone')
  43. def get_one(self, workspace_id, **kwargs):
  44. """
  45. :param workspace_id: Displayed workspace id
  46. :param kwargs:
  47. * show_deleted: bool: Display deleted contents or hide them if False
  48. * show_archived: bool: Display archived contents or hide them
  49. if False
  50. """
  51. show_deleted = str_as_bool(kwargs.get('show_deleted', False))
  52. show_archived = str_as_bool(kwargs.get('show_archived', ''))
  53. user = tmpl_context.current_user
  54. workspace_api = WorkspaceApi(user)
  55. workspace = workspace_api.get_one(workspace_id)
  56. unread_contents = ContentApi(user).get_last_unread(None,
  57. ContentType.Any,
  58. workspace=workspace)
  59. current_user_content = Context(CTX.CURRENT_USER).toDict(user)
  60. current_user_content.roles.sort(key=lambda role: role.workspace.name)
  61. dictified_current_user = Context(CTX.CURRENT_USER).toDict(user)
  62. dictified_folders = self.folders.get_all_fake(workspace).result
  63. fake_api = DictLikeClass(
  64. last_unread=Context(CTX.CONTENT_LIST).toDict(unread_contents,
  65. 'contents',
  66. 'nb'),
  67. current_user=dictified_current_user,
  68. current_workspace_folders=dictified_folders,
  69. current_user_workspace_role=workspace.get_user_role(user)
  70. )
  71. fake_api.sub_items = Context(CTX.FOLDER_CONTENT_LIST).toDict(
  72. # TODO BS 20161209: Is the correct way to grab folders? No use API?
  73. workspace.get_valid_children(
  74. ContentApi.DISPLAYABLE_CONTENTS,
  75. show_deleted=show_deleted,
  76. show_archived=show_archived,
  77. )
  78. )
  79. videoconf_enabled = CFG.get_instance().JITSI_MEET_ACTIVATED
  80. dictified_workspace = Context(CTX.WORKSPACE).toDict(workspace, 'workspace')
  81. webdav_url = CFG.get_instance().WSGIDAV_CLIENT_BASE_URL
  82. website_protocol = urlparse(CFG.get_instance().WEBSITE_BASE_URL).scheme
  83. dav_protocol = 'dav'
  84. if website_protocol == "https":
  85. dav_protocol = 'davs'
  86. return DictLikeClass(
  87. result=dictified_workspace,
  88. fake_api=fake_api,
  89. webdav_url=webdav_url,
  90. videoconf_enabled=videoconf_enabled,
  91. website_protocol = website_protocol,
  92. dav_protocol = dav_protocol,
  93. show_deleted=show_deleted,
  94. show_archived=show_archived,
  95. )
  96. @tg.expose('json')
  97. def treeview_root(self, id='#',
  98. current_id=None,
  99. all_workspaces=True,
  100. folder_allowed_content_types='',
  101. ignore_id=None,
  102. ignore_workspace_id=None):
  103. all_workspaces = bool(int(all_workspaces))
  104. # ignore_workspace_id is a string like 3,12,78,15
  105. ignored_ids = [int(id) for id in ignore_workspace_id.split(',')] if ignore_workspace_id else None
  106. if not current_id:
  107. # Default case is to return list of workspaces
  108. api = WorkspaceApi(tmpl_context.current_user)
  109. workspaces = api.get_all_for_user(tmpl_context.current_user,
  110. ignored_ids)
  111. dictified_workspaces = Context(CTX.MENU_API).toDict(workspaces, 'd')
  112. return dictified_workspaces
  113. allowed_content_types = ContentType.allowed_types_from_str(folder_allowed_content_types)
  114. ignored_item_ids = [int(ignore_id)] if ignore_id else []
  115. # Now complex case: we must return a structured tree
  116. # including the selected node, all parents (and their siblings)
  117. workspace, content = convert_id_into_instances(current_id)
  118. # The following step allow to select the parent folder when content itself is not visible in the treeview
  119. if content and content.type!=ContentType.Folder and CFG.CST.TREEVIEW_ALL!=CFG.get_instance().WEBSITE_TREEVIEW_CONTENT:
  120. content = content.parent if content.parent else None
  121. # This is the init of the recursive-like build of the tree
  122. content_parent = content
  123. tree_items = []
  124. # The first step allow to load child of selected item
  125. # (for example, when you select a folder in the windows explorer,
  126. # then the selected folder is expanded by default)
  127. content_api = ContentApi(tmpl_context.current_user)
  128. child_folders = content_api.get_child_folders(content_parent, workspace, allowed_content_types, ignored_item_ids)
  129. if len(child_folders)>0:
  130. first_child = child_folders[0]
  131. content_parent, tree_items = self._build_sibling_list_of_tree_items(workspace, first_child, tree_items, False, allowed_content_types, ignored_item_ids)
  132. content_parent, tree_items = self._build_sibling_list_of_tree_items(workspace, content_parent, tree_items, True, allowed_content_types, ignored_item_ids)
  133. while content_parent:
  134. # Do the same for the parent level
  135. content_parent, tree_items = self._build_sibling_list_of_tree_items(workspace, content_parent, tree_items)
  136. # Now, we have a tree_items list that is the root folders list,
  137. # so we now have to put it as a child of a list of workspaces
  138. should_select_workspace = not content
  139. full_tree = self._build_sibling_list_of_workspaces(workspace, tree_items, should_select_workspace, all_workspaces)
  140. return Context(CTX.MENU_API_BUILD_FROM_TREE_ITEM).toDict(full_tree, 'd')
  141. def _build_sibling_list_of_workspaces(self, workspace: Workspace, child_contents: [NodeTreeItem], select_active_workspace = False, all_workspaces = True) -> [NodeTreeItem]:
  142. root_items = []
  143. api = WorkspaceApi(tmpl_context.current_user)
  144. workspaces = api.get_all_for_user(tmpl_context.current_user)
  145. if not all_workspaces:
  146. # Show only current workspace - this is used for "move item" screen
  147. # which must not allow to move from a workspace to another
  148. item = NodeTreeItem(workspace, child_contents)
  149. item.is_selected = select_active_workspace
  150. root_items.append(item)
  151. else:
  152. for workspace_cursor in workspaces:
  153. item = None
  154. if workspace_cursor==workspace:
  155. item = NodeTreeItem(workspace_cursor, child_contents)
  156. else:
  157. item = NodeTreeItem(workspace_cursor, [])
  158. item.is_selected = select_active_workspace and workspace_cursor==workspace
  159. root_items.append(item)
  160. return root_items
  161. def _build_sibling_list_of_tree_items(self,
  162. workspace: Workspace,
  163. content: Content,
  164. children: [NodeTreeItem],
  165. select_active_node = False,
  166. allowed_content_types: list = [],
  167. ignored_item_ids: list = []) -> (Content, [NodeTreeItem]):
  168. api = ContentApi(tmpl_context.current_user)
  169. tree_items = []
  170. parent = content.parent if content else None
  171. viewable_content_types = self._get_treeviewable_content_types_or_none()
  172. child_contents = api.get_child_folders(parent, workspace, allowed_content_types, ignored_item_ids, viewable_content_types)
  173. for child in child_contents:
  174. children_to_add = children if child==content else []
  175. if child==content and select_active_node:
  176. # The item is the requested node, so we select it
  177. is_selected = True
  178. elif content not in child_contents and select_active_node and child==content:
  179. # The item is not present in the list, so we select its parent node
  180. is_selected = True
  181. else:
  182. is_selected = False
  183. new_item = NodeTreeItem(child, children_to_add, is_selected)
  184. tree_items.append(new_item)
  185. # This allow to show contents and folders group by type
  186. tree_items = ContentApi.sort_tree_items(tree_items)
  187. return parent, tree_items
  188. @tg.expose('json')
  189. def treeview_children(self, id='#',
  190. ignore_id=None,
  191. allowed_content_types = None):
  192. """
  193. id must be "#" or something like "workspace_3__document_8"
  194. """
  195. if id=='#':
  196. return self.treeview_root()
  197. ignore_item_ids = [int(ignore_id)] if ignore_id else []
  198. workspace, content = convert_id_into_instances(id)
  199. viewable_content_types = []
  200. if allowed_content_types:
  201. viewable_content_types = allowed_content_types.split(',')
  202. else:
  203. viewable_content_types = self._get_treeviewable_content_types_or_none()
  204. contents = ContentApi(tmpl_context.current_user).get_child_folders(content, workspace, [], ignore_item_ids, viewable_content_types)
  205. # This allow to show contents and folders group by type
  206. sorted_contents = ContentApi.sort_content(contents)
  207. dictified_contents = Context(CTX.MENU_API).toDict(sorted_contents, 'd')
  208. return dictified_contents
  209. def _get_treeviewable_content_types_or_none(self):
  210. if CFG.get_instance().WEBSITE_TREEVIEW_CONTENT==CFG.CST.TREEVIEW_ALL:
  211. return ContentType.Any
  212. return None # None means "workspaces and folders"