context_models.py 15KB

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