context_models.py 16KB

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