context_models.py 16KB

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