context_models.py 14KB

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