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