context_models.py 16KB

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