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