context_models.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  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 WorkspaceAndContentRevisionPath(object):
  37. """
  38. Paths params with workspace id and content_id model
  39. """
  40. def __init__(self, workspace_id: int, content_id: int, revision_id) -> None:
  41. self.content_id = content_id
  42. self.revision_id = revision_id
  43. self.workspace_id = workspace_id
  44. class ContentPreviewSizedPath(object):
  45. """
  46. Paths params with workspace id and content_id, width, heigth
  47. """
  48. def __init__(self, workspace_id: int, content_id: int, width: int, height: int) -> None: # nopep8
  49. self.content_id = content_id
  50. self.workspace_id = workspace_id
  51. self.width = width
  52. self.height = height
  53. class RevisionPreviewSizedPath(object):
  54. """
  55. Paths params with workspace id and content_id, revision_id width, heigth
  56. """
  57. def __init__(self, workspace_id: int, content_id: int, revision_id: int, width: int, height: int) -> None: # nopep8
  58. self.content_id = content_id
  59. self.revision_id = revision_id
  60. self.workspace_id = workspace_id
  61. self.width = width
  62. self.height = height
  63. class CommentPath(object):
  64. """
  65. Paths params with workspace id and content_id and comment_id model
  66. """
  67. def __init__(
  68. self,
  69. workspace_id: int,
  70. content_id: int,
  71. comment_id: int
  72. ) -> None:
  73. self.content_id = content_id
  74. self.workspace_id = workspace_id
  75. self.comment_id = comment_id
  76. class PageQuery(object):
  77. """
  78. Page query model
  79. """
  80. def __init__(
  81. self,
  82. page: int = 0
  83. ):
  84. self.page = page
  85. class ContentFilter(object):
  86. """
  87. Content filter model
  88. """
  89. def __init__(
  90. self,
  91. parent_id: int = None,
  92. show_archived: int = 0,
  93. show_deleted: int = 0,
  94. show_active: int = 1,
  95. ) -> None:
  96. self.parent_id = parent_id
  97. self.show_archived = bool(show_archived)
  98. self.show_deleted = bool(show_deleted)
  99. self.show_active = bool(show_active)
  100. class ContentCreation(object):
  101. """
  102. Content creation model
  103. """
  104. def __init__(
  105. self,
  106. label: str,
  107. content_type: str,
  108. ) -> None:
  109. self.label = label
  110. self.content_type = content_type
  111. class CommentCreation(object):
  112. """
  113. Comment creation model
  114. """
  115. def __init__(
  116. self,
  117. raw_content: str,
  118. ) -> None:
  119. self.raw_content = raw_content
  120. class SetContentStatus(object):
  121. """
  122. Set content status
  123. """
  124. def __init__(
  125. self,
  126. status: str,
  127. ) -> None:
  128. self.status = status
  129. class TextBasedContentUpdate(object):
  130. """
  131. TextBasedContent update model
  132. """
  133. def __init__(
  134. self,
  135. label: str,
  136. raw_content: str,
  137. ) -> None:
  138. self.label = label
  139. self.raw_content = raw_content
  140. class UserInContext(object):
  141. """
  142. Interface to get User data and User data related to context.
  143. """
  144. def __init__(self, user: User, dbsession: Session, config: CFG):
  145. self.user = user
  146. self.dbsession = dbsession
  147. self.config = config
  148. # Default
  149. @property
  150. def email(self) -> str:
  151. return self.user.email
  152. @property
  153. def user_id(self) -> int:
  154. return self.user.user_id
  155. @property
  156. def public_name(self) -> str:
  157. return self.display_name
  158. @property
  159. def display_name(self) -> str:
  160. return self.user.display_name
  161. @property
  162. def created(self) -> datetime:
  163. return self.user.created
  164. @property
  165. def is_active(self) -> bool:
  166. return self.user.is_active
  167. @property
  168. def timezone(self) -> str:
  169. return self.user.timezone
  170. @property
  171. def profile(self) -> Profile:
  172. return self.user.profile.name
  173. # Context related
  174. @property
  175. def calendar_url(self) -> typing.Optional[str]:
  176. # TODO - G-M - 20-04-2018 - [Calendar] Replace calendar code to get
  177. # url calendar url.
  178. #
  179. # from tracim.lib.calendar import CalendarManager
  180. # calendar_manager = CalendarManager(None)
  181. # return calendar_manager.get_workspace_calendar_url(self.workspace_id)
  182. return None
  183. @property
  184. def avatar_url(self) -> typing.Optional[str]:
  185. # TODO - G-M - 20-04-2018 - [Avatar] Add user avatar feature
  186. return None
  187. class WorkspaceInContext(object):
  188. """
  189. Interface to get Workspace data and Workspace data related to context.
  190. """
  191. def __init__(self, workspace: Workspace, dbsession: Session, config: CFG):
  192. self.workspace = workspace
  193. self.dbsession = dbsession
  194. self.config = config
  195. @property
  196. def workspace_id(self) -> int:
  197. """
  198. numeric id of the workspace.
  199. """
  200. return self.workspace.workspace_id
  201. @property
  202. def id(self) -> int:
  203. """
  204. alias of workspace_id
  205. """
  206. return self.workspace_id
  207. @property
  208. def label(self) -> str:
  209. """
  210. get workspace label
  211. """
  212. return self.workspace.label
  213. @property
  214. def description(self) -> str:
  215. """
  216. get workspace description
  217. """
  218. return self.workspace.description
  219. @property
  220. def slug(self) -> str:
  221. """
  222. get workspace slug
  223. """
  224. return slugify(self.workspace.label)
  225. @property
  226. def sidebar_entries(self) -> typing.List[WorkspaceMenuEntry]:
  227. """
  228. get sidebar entries, those depends on activated apps.
  229. """
  230. # TODO - G.M - 22-05-2018 - Rework on this in
  231. # order to not use hardcoded list
  232. # list should be able to change (depending on activated/disabled
  233. # apps)
  234. return default_workspace_menu_entry(self.workspace)
  235. class UserRoleWorkspaceInContext(object):
  236. """
  237. Interface to get UserRoleInWorkspace data and related content
  238. """
  239. def __init__(
  240. self,
  241. user_role: UserRoleInWorkspace,
  242. dbsession: Session,
  243. config: CFG,
  244. )-> None:
  245. self.user_role = user_role
  246. self.dbsession = dbsession
  247. self.config = config
  248. @property
  249. def user_id(self) -> int:
  250. """
  251. User who has the role has this id
  252. :return: user id as integer
  253. """
  254. return self.user_role.user_id
  255. @property
  256. def workspace_id(self) -> int:
  257. """
  258. This role apply only on the workspace with this workspace_id
  259. :return: workspace id as integer
  260. """
  261. return self.user_role.workspace_id
  262. # TODO - G.M - 23-05-2018 - Check the API spec for this this !
  263. @property
  264. def role_id(self) -> int:
  265. """
  266. role as int id, each value refer to a different role.
  267. """
  268. return self.user_role.role
  269. @property
  270. def role(self) -> str:
  271. return self.role_slug
  272. @property
  273. def role_slug(self) -> str:
  274. """
  275. simple name of the role of the user.
  276. can be anything from UserRoleInWorkspace SLUG, like
  277. 'not_applicable', 'reader',
  278. 'contributor', 'content-manager', 'workspace-manager'
  279. :return: user workspace role as slug.
  280. """
  281. return UserRoleInWorkspace.SLUG[self.user_role.role]
  282. @property
  283. def user(self) -> UserInContext:
  284. """
  285. User who has this role, with context data
  286. :return: UserInContext object
  287. """
  288. return UserInContext(
  289. self.user_role.user,
  290. self.dbsession,
  291. self.config
  292. )
  293. @property
  294. def workspace(self) -> WorkspaceInContext:
  295. """
  296. Workspace related to this role, with his context data
  297. :return: WorkspaceInContext object
  298. """
  299. return WorkspaceInContext(
  300. self.user_role.workspace,
  301. self.dbsession,
  302. self.config
  303. )
  304. class ContentInContext(object):
  305. """
  306. Interface to get Content data and Content data related to context.
  307. """
  308. def __init__(self, content: Content, dbsession: Session, config: CFG):
  309. self.content = content
  310. self.dbsession = dbsession
  311. self.config = config
  312. # Default
  313. @property
  314. def content_id(self) -> int:
  315. return self.content.content_id
  316. @property
  317. def id(self) -> int:
  318. return self.content_id
  319. @property
  320. def parent_id(self) -> int:
  321. """
  322. Return parent_id of the content
  323. """
  324. return self.content.parent_id
  325. @property
  326. def workspace_id(self) -> int:
  327. return self.content.workspace_id
  328. @property
  329. def label(self) -> str:
  330. return self.content.label
  331. @property
  332. def content_type(self) -> str:
  333. content_type = ContentType(self.content.type)
  334. return content_type.slug
  335. @property
  336. def sub_content_types(self) -> typing.List[str]:
  337. return [_type.slug for _type in self.content.get_allowed_content_types()] # nopep8
  338. @property
  339. def status(self) -> str:
  340. return self.content.status
  341. @property
  342. def is_archived(self):
  343. return self.content.is_archived
  344. @property
  345. def is_deleted(self):
  346. return self.content.is_deleted
  347. @property
  348. def raw_content(self):
  349. return self.content.description
  350. @property
  351. def author(self):
  352. return UserInContext(
  353. dbsession=self.dbsession,
  354. config=self.config,
  355. user=self.content.first_revision.owner
  356. )
  357. @property
  358. def current_revision_id(self):
  359. return self.content.revision_id
  360. @property
  361. def created(self):
  362. return self.content.created
  363. @property
  364. def modified(self):
  365. return self.updated
  366. @property
  367. def updated(self):
  368. return self.content.updated
  369. @property
  370. def last_modifier(self):
  371. return UserInContext(
  372. dbsession=self.dbsession,
  373. config=self.config,
  374. user=self.content.last_revision.owner
  375. )
  376. # Context-related
  377. @property
  378. def show_in_ui(self):
  379. # TODO - G.M - 31-05-2018 - Enable Show_in_ui params
  380. # if false, then do not show content in the treeview.
  381. # This may his maybe used for specific contents or for sub-contents.
  382. # Default is True.
  383. # In first version of the API, this field is always True
  384. return True
  385. @property
  386. def slug(self):
  387. return slugify(self.content.label)
  388. class RevisionInContext(object):
  389. """
  390. Interface to get Content data and Content data related to context.
  391. """
  392. def __init__(self, content_revision: ContentRevisionRO, dbsession: Session, config: CFG):
  393. assert content_revision is not None
  394. self.revision = content_revision
  395. self.dbsession = dbsession
  396. self.config = config
  397. # Default
  398. @property
  399. def content_id(self) -> int:
  400. return self.revision.content_id
  401. @property
  402. def id(self) -> int:
  403. return self.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)