context_models.py 15KB

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