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