context_models.py 15KB

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