context_models.py 16KB

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