context_models.py 15KB

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