context_models.py 16KB

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