context_models.py 15KB

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