context_models.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. # coding=utf-8
  2. import typing
  3. from datetime import datetime
  4. from slugify import slugify
  5. from sqlalchemy.orm import Session
  6. from tracim import CFG
  7. from tracim.models import User
  8. from tracim.models.auth import Profile
  9. from tracim.models.data import Content
  10. from tracim.models.data import ContentRevisionRO
  11. from tracim.models.data import Workspace, UserRoleInWorkspace
  12. from tracim.models.workspace_menu_entries import default_workspace_menu_entry
  13. from tracim.models.workspace_menu_entries import WorkspaceMenuEntry
  14. from tracim.models.contents import ContentTypeLegacy as ContentType
  15. class MoveParams(object):
  16. """
  17. Json body params for move action model
  18. """
  19. def __init__(self, new_parent_id: str, new_workspace_id: str = None) -> None: # nopep8
  20. self.new_parent_id = new_parent_id
  21. self.new_workspace_id = new_workspace_id
  22. class LoginCredentials(object):
  23. """
  24. Login credentials model for login model
  25. """
  26. def __init__(self, email: str, password: str) -> None:
  27. self.email = email
  28. self.password = password
  29. class WorkspaceAndContentPath(object):
  30. """
  31. Paths params with workspace id and content_id model
  32. """
  33. def __init__(self, workspace_id: int, content_id: int) -> None:
  34. self.content_id = content_id
  35. self.workspace_id = workspace_id
  36. class CommentPath(object):
  37. """
  38. Paths params with workspace id and content_id and comment_id model
  39. """
  40. def __init__(
  41. self,
  42. workspace_id: int,
  43. content_id: int,
  44. comment_id: int
  45. ) -> None:
  46. self.content_id = content_id
  47. self.workspace_id = workspace_id
  48. self.comment_id = comment_id
  49. class ContentFilter(object):
  50. """
  51. Content filter model
  52. """
  53. def __init__(
  54. self,
  55. workspace_id: int = None,
  56. parent_id: int = None,
  57. show_archived: int = 0,
  58. show_deleted: int = 0,
  59. show_active: int = 1,
  60. content_type: str = None,
  61. offset: int = None,
  62. limit: int = None,
  63. ) -> None:
  64. self.parent_id = parent_id
  65. self.workspace_id = workspace_id
  66. self.show_archived = bool(show_archived)
  67. self.show_deleted = bool(show_deleted)
  68. self.show_active = bool(show_active)
  69. self.limit = limit
  70. self.offset = offset
  71. self.content_type = content_type
  72. class ContentCreation(object):
  73. """
  74. Content creation model
  75. """
  76. def __init__(
  77. self,
  78. label: str,
  79. content_type: str,
  80. ) -> None:
  81. self.label = label
  82. self.content_type = content_type
  83. class CommentCreation(object):
  84. """
  85. Comment creation model
  86. """
  87. def __init__(
  88. self,
  89. raw_content: str,
  90. ) -> None:
  91. self.raw_content = raw_content
  92. class SetContentStatus(object):
  93. """
  94. Set content status
  95. """
  96. def __init__(
  97. self,
  98. status: str,
  99. ) -> None:
  100. self.status = status
  101. class TextBasedContentUpdate(object):
  102. """
  103. TextBasedContent update model
  104. """
  105. def __init__(
  106. self,
  107. label: str,
  108. raw_content: str,
  109. ) -> None:
  110. self.label = label
  111. self.raw_content = raw_content
  112. class UserInContext(object):
  113. """
  114. Interface to get User data and User data related to context.
  115. """
  116. def __init__(self, user: User, dbsession: Session, config: CFG):
  117. self.user = user
  118. self.dbsession = dbsession
  119. self.config = config
  120. # Default
  121. @property
  122. def email(self) -> str:
  123. return self.user.email
  124. @property
  125. def user_id(self) -> int:
  126. return self.user.user_id
  127. @property
  128. def public_name(self) -> str:
  129. return self.display_name
  130. @property
  131. def display_name(self) -> str:
  132. return self.user.display_name
  133. @property
  134. def created(self) -> datetime:
  135. return self.user.created
  136. @property
  137. def is_active(self) -> bool:
  138. return self.user.is_active
  139. @property
  140. def timezone(self) -> str:
  141. return self.user.timezone
  142. @property
  143. def profile(self) -> Profile:
  144. return self.user.profile.name
  145. # Context related
  146. @property
  147. def calendar_url(self) -> typing.Optional[str]:
  148. # TODO - G-M - 20-04-2018 - [Calendar] Replace calendar code to get
  149. # url calendar url.
  150. #
  151. # from tracim.lib.calendar import CalendarManager
  152. # calendar_manager = CalendarManager(None)
  153. # return calendar_manager.get_workspace_calendar_url(self.workspace_id)
  154. return None
  155. @property
  156. def avatar_url(self) -> typing.Optional[str]:
  157. # TODO - G-M - 20-04-2018 - [Avatar] Add user avatar feature
  158. return None
  159. class WorkspaceInContext(object):
  160. """
  161. Interface to get Workspace data and Workspace data related to context.
  162. """
  163. def __init__(self, workspace: Workspace, dbsession: Session, config: CFG):
  164. self.workspace = workspace
  165. self.dbsession = dbsession
  166. self.config = config
  167. @property
  168. def workspace_id(self) -> int:
  169. """
  170. numeric id of the workspace.
  171. """
  172. return self.workspace.workspace_id
  173. @property
  174. def id(self) -> int:
  175. """
  176. alias of workspace_id
  177. """
  178. return self.workspace_id
  179. @property
  180. def label(self) -> str:
  181. """
  182. get workspace label
  183. """
  184. return self.workspace.label
  185. @property
  186. def description(self) -> str:
  187. """
  188. get workspace description
  189. """
  190. return self.workspace.description
  191. @property
  192. def slug(self) -> str:
  193. """
  194. get workspace slug
  195. """
  196. return slugify(self.workspace.label)
  197. @property
  198. def sidebar_entries(self) -> typing.List[WorkspaceMenuEntry]:
  199. """
  200. get sidebar entries, those depends on activated apps.
  201. """
  202. # TODO - G.M - 22-05-2018 - Rework on this in
  203. # order to not use hardcoded list
  204. # list should be able to change (depending on activated/disabled
  205. # apps)
  206. return default_workspace_menu_entry(self.workspace)
  207. class UserRoleWorkspaceInContext(object):
  208. """
  209. Interface to get UserRoleInWorkspace data and related content
  210. """
  211. def __init__(
  212. self,
  213. user_role: UserRoleInWorkspace,
  214. dbsession: Session,
  215. config: CFG,
  216. )-> None:
  217. self.user_role = user_role
  218. self.dbsession = dbsession
  219. self.config = config
  220. @property
  221. def user_id(self) -> int:
  222. """
  223. User who has the role has this id
  224. :return: user id as integer
  225. """
  226. return self.user_role.user_id
  227. @property
  228. def workspace_id(self) -> int:
  229. """
  230. This role apply only on the workspace with this workspace_id
  231. :return: workspace id as integer
  232. """
  233. return self.user_role.workspace_id
  234. # TODO - G.M - 23-05-2018 - Check the API spec for this this !
  235. @property
  236. def role_id(self) -> int:
  237. """
  238. role as int id, each value refer to a different role.
  239. """
  240. return self.user_role.role
  241. @property
  242. def role(self) -> str:
  243. return self.role_slug
  244. @property
  245. def role_slug(self) -> str:
  246. """
  247. simple name of the role of the user.
  248. can be anything from UserRoleInWorkspace SLUG, like
  249. 'not_applicable', 'reader',
  250. 'contributor', 'content-manager', 'workspace-manager'
  251. :return: user workspace role as slug.
  252. """
  253. return UserRoleInWorkspace.SLUG[self.user_role.role]
  254. @property
  255. def user(self) -> UserInContext:
  256. """
  257. User who has this role, with context data
  258. :return: UserInContext object
  259. """
  260. return UserInContext(
  261. self.user_role.user,
  262. self.dbsession,
  263. self.config
  264. )
  265. @property
  266. def workspace(self) -> WorkspaceInContext:
  267. """
  268. Workspace related to this role, with his context data
  269. :return: WorkspaceInContext object
  270. """
  271. return WorkspaceInContext(
  272. self.user_role.workspace,
  273. self.dbsession,
  274. self.config
  275. )
  276. class ContentInContext(object):
  277. """
  278. Interface to get Content data and Content data related to context.
  279. """
  280. def __init__(self, content: Content, dbsession: Session, config: CFG, user: User=None): # nopep8
  281. self.content = content
  282. self.dbsession = dbsession
  283. self.config = config
  284. self._user = user
  285. # Default
  286. @property
  287. def content_id(self) -> int:
  288. return self.content.content_id
  289. @property
  290. def parent_id(self) -> int:
  291. """
  292. Return parent_id of the content
  293. """
  294. return self.content.parent_id
  295. @property
  296. def workspace_id(self) -> int:
  297. return self.content.workspace_id
  298. @property
  299. def label(self) -> str:
  300. return self.content.label
  301. @property
  302. def content_type(self) -> str:
  303. content_type = ContentType(self.content.type)
  304. return content_type.slug
  305. @property
  306. def sub_content_types(self) -> typing.List[str]:
  307. return [_type.slug for _type in self.content.get_allowed_content_types()] # nopep8
  308. @property
  309. def status(self) -> str:
  310. return self.content.status
  311. @property
  312. def is_archived(self):
  313. return self.content.is_archived
  314. @property
  315. def is_deleted(self):
  316. return self.content.is_deleted
  317. @property
  318. def raw_content(self):
  319. return self.content.description
  320. @property
  321. def author(self):
  322. return UserInContext(
  323. dbsession=self.dbsession,
  324. config=self.config,
  325. user=self.content.first_revision.owner
  326. )
  327. @property
  328. def current_revision_id(self):
  329. return self.content.revision_id
  330. @property
  331. def created(self):
  332. return self.content.created
  333. @property
  334. def modified(self):
  335. return self.updated
  336. @property
  337. def updated(self):
  338. return self.content.updated
  339. @property
  340. def last_modifier(self):
  341. return UserInContext(
  342. dbsession=self.dbsession,
  343. config=self.config,
  344. user=self.content.last_revision.owner
  345. )
  346. # Context-related
  347. @property
  348. def show_in_ui(self):
  349. # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
  350. # if false, then do not show content in the treeview.
  351. # This may his maybe used for specific contents or for sub-contents.
  352. # Default is True.
  353. # In first version of the API, this field is always True
  354. return True
  355. @property
  356. def slug(self):
  357. return slugify(self.content.label)
  358. @property
  359. def read_by_user(self):
  360. assert self._user
  361. return self.content.has_new_information_for(self._user)
  362. class RevisionInContext(object):
  363. """
  364. Interface to get Content data and Content data related to context.
  365. """
  366. def __init__(self, content_revision: ContentRevisionRO, dbsession: Session, config: CFG):
  367. assert content_revision is not None
  368. self.revision = content_revision
  369. self.dbsession = dbsession
  370. self.config = config
  371. # Default
  372. @property
  373. def content_id(self) -> int:
  374. return self.revision.content_id
  375. @property
  376. def parent_id(self) -> int:
  377. """
  378. Return parent_id of the content
  379. """
  380. return self.revision.parent_id
  381. @property
  382. def workspace_id(self) -> int:
  383. return self.revision.workspace_id
  384. @property
  385. def label(self) -> str:
  386. return self.revision.label
  387. @property
  388. def content_type(self) -> str:
  389. content_type = ContentType(self.revision.type)
  390. if content_type:
  391. return content_type.slug
  392. else:
  393. return None
  394. @property
  395. def sub_content_types(self) -> typing.List[str]:
  396. return [_type.slug for _type
  397. in self.revision.node.get_allowed_content_types()]
  398. @property
  399. def status(self) -> str:
  400. return self.revision.status
  401. @property
  402. def is_archived(self) -> bool:
  403. return self.revision.is_archived
  404. @property
  405. def is_deleted(self) -> bool:
  406. return self.revision.is_deleted
  407. @property
  408. def raw_content(self) -> str:
  409. return self.revision.description
  410. @property
  411. def author(self) -> UserInContext:
  412. return UserInContext(
  413. dbsession=self.dbsession,
  414. config=self.config,
  415. user=self.revision.owner
  416. )
  417. @property
  418. def revision_id(self) -> int:
  419. return self.revision.revision_id
  420. @property
  421. def created(self) -> datetime:
  422. return self.updated
  423. @property
  424. def modified(self) -> datetime:
  425. return self.updated
  426. @property
  427. def updated(self) -> datetime:
  428. return self.revision.updated
  429. @property
  430. def next_revision(self) -> typing.Optional[ContentRevisionRO]:
  431. """
  432. Get next revision (later revision)
  433. :return: next_revision
  434. """
  435. next_revision = None
  436. revisions = self.revision.node.revisions
  437. # INFO - G.M - 2018-06-177 - Get revisions more recent that
  438. # current one
  439. next_revisions = [
  440. revision for revision in revisions
  441. if revision.revision_id > self.revision.revision_id
  442. ]
  443. if next_revisions:
  444. # INFO - G.M - 2018-06-177 -sort revisions by date
  445. sorted_next_revisions = sorted(
  446. next_revisions,
  447. key=lambda revision: revision.updated
  448. )
  449. # INFO - G.M - 2018-06-177 - return only next revision
  450. return sorted_next_revisions[0]
  451. else:
  452. return None
  453. @property
  454. def comment_ids(self) -> typing.List[int]:
  455. """
  456. Get list of ids of all current revision related comments
  457. :return: list of comments ids
  458. """
  459. comments = self.revision.node.get_comments()
  460. # INFO - G.M - 2018-06-177 - Get comments more recent than revision.
  461. revision_comments = [
  462. comment for comment in comments
  463. if comment.created > self.revision.updated
  464. ]
  465. if self.next_revision:
  466. # INFO - G.M - 2018-06-177 - if there is a revision more recent
  467. # than current remove comments from theses rev (comments older
  468. # than next_revision.)
  469. revision_comments = [
  470. comment for comment in revision_comments
  471. if comment.created < self.next_revision.updated
  472. ]
  473. sorted_revision_comments = sorted(
  474. revision_comments,
  475. key=lambda revision: revision.created
  476. )
  477. comment_ids = []
  478. for comment in sorted_revision_comments:
  479. comment_ids.append(comment.content_id)
  480. return comment_ids
  481. # Context-related
  482. @property
  483. def show_in_ui(self) -> bool:
  484. # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
  485. # if false, then do not show content in the treeview.
  486. # This may his maybe used for specific contents or for sub-contents.
  487. # Default is True.
  488. # In first version of the API, this field is always True
  489. return True
  490. @property
  491. def slug(self) -> str:
  492. return slugify(self.revision.label)