context_models.py 15KB

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