context_models.py 16KB

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