context_models.py 15KB

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