context_models.py 20KB

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