context_models.py 16KB

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