context_models.py 16KB

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