schemas.py 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. # coding=utf-8
  2. import marshmallow
  3. from marshmallow import post_load
  4. from marshmallow.validate import OneOf
  5. from marshmallow.validate import Range
  6. from tracim.lib.utils.utils import DATETIME_FORMAT
  7. from tracim.models.auth import Profile
  8. from tracim.models.contents import GlobalStatus
  9. from tracim.models.contents import open_status
  10. from tracim.models.contents import ContentTypeLegacy as ContentType
  11. from tracim.models.contents import ContentStatusLegacy as ContentStatus
  12. from tracim.models.context_models import ActiveContentFilter
  13. from tracim.models.context_models import ContentIdsQuery
  14. from tracim.models.context_models import UserWorkspaceAndContentPath
  15. from tracim.models.context_models import ContentCreation
  16. from tracim.models.context_models import WorkspaceMemberInvitation
  17. from tracim.models.context_models import WorkspaceUpdate
  18. from tracim.models.context_models import RoleUpdate
  19. from tracim.models.context_models import CommentCreation
  20. from tracim.models.context_models import TextBasedContentUpdate
  21. from tracim.models.context_models import SetContentStatus
  22. from tracim.models.context_models import CommentPath
  23. from tracim.models.context_models import MoveParams
  24. from tracim.models.context_models import WorkspaceAndContentPath
  25. from tracim.models.context_models import WorkspaceAndUserPath
  26. from tracim.models.context_models import ContentFilter
  27. from tracim.models.context_models import LoginCredentials
  28. from tracim.models.data import UserRoleInWorkspace
  29. from tracim.models.data import ActionDescription
  30. class UserDigestSchema(marshmallow.Schema):
  31. """
  32. Simple user schema
  33. """
  34. user_id = marshmallow.fields.Int(dump_only=True, example=3)
  35. avatar_url = marshmallow.fields.Url(
  36. allow_none=True,
  37. example="/api/v2/assets/avatars/suri-cate.jpg",
  38. description="avatar_url is the url to the image file. "
  39. "If no avatar, then set it to null "
  40. "(and frontend will interpret this with a default avatar)",
  41. )
  42. public_name = marshmallow.fields.String(
  43. example='Suri Cate',
  44. )
  45. class UserSchema(UserDigestSchema):
  46. """
  47. Complete user schema
  48. """
  49. email = marshmallow.fields.Email(
  50. required=True,
  51. example='suri.cate@algoo.fr'
  52. )
  53. created = marshmallow.fields.DateTime(
  54. format=DATETIME_FORMAT,
  55. description='User account creation date',
  56. )
  57. is_active = marshmallow.fields.Bool(
  58. example=True,
  59. # TODO - G.M - Explains this value.
  60. )
  61. # TODO - G.M - 17-04-2018 - Restrict timezone values
  62. timezone = marshmallow.fields.String(
  63. example="Paris/Europe",
  64. )
  65. # TODO - G.M - 17-04-2018 - check this, relative url allowed ?
  66. caldav_url = marshmallow.fields.Url(
  67. allow_none=True,
  68. relative=True,
  69. attribute='calendar_url',
  70. example="/api/v2/calendar/user/3.ics/",
  71. description="The url for calendar CalDAV direct access",
  72. )
  73. profile = marshmallow.fields.String(
  74. attribute='profile',
  75. validate=OneOf(Profile._NAME),
  76. example='managers',
  77. )
  78. class Meta:
  79. description = 'User account of Tracim'
  80. # Path Schemas
  81. class UserIdPathSchema(marshmallow.Schema):
  82. user_id = marshmallow.fields.Int(
  83. example=3,
  84. required=True,
  85. description='id of a valid user',
  86. validate=Range(min=1, error="Value must be greater than 0"),
  87. )
  88. class WorkspaceIdPathSchema(marshmallow.Schema):
  89. workspace_id = marshmallow.fields.Int(
  90. example=4,
  91. required=True,
  92. description='id of a valid workspace',
  93. validate=Range(min=1, error="Value must be greater than 0"),
  94. )
  95. class ContentIdPathSchema(marshmallow.Schema):
  96. content_id = marshmallow.fields.Int(
  97. example=6,
  98. required=True,
  99. description='id of a valid content',
  100. validate=Range(min=1, error="Value must be greater than 0"),
  101. )
  102. class WorkspaceAndUserIdPathSchema(
  103. UserIdPathSchema,
  104. WorkspaceIdPathSchema
  105. ):
  106. @post_load
  107. def make_path_object(self, data):
  108. return WorkspaceAndUserPath(**data)
  109. class WorkspaceAndContentIdPathSchema(
  110. WorkspaceIdPathSchema,
  111. ContentIdPathSchema
  112. ):
  113. @post_load
  114. def make_path_object(self, data):
  115. return WorkspaceAndContentPath(**data)
  116. class UserWorkspaceAndContentIdPathSchema(
  117. UserIdPathSchema,
  118. WorkspaceIdPathSchema,
  119. ContentIdPathSchema,
  120. ):
  121. @post_load
  122. def make_path_object(self, data):
  123. return UserWorkspaceAndContentPath(**data)
  124. class UserWorkspaceIdPathSchema(
  125. UserIdPathSchema,
  126. WorkspaceIdPathSchema,
  127. ):
  128. @post_load
  129. def make_path_object(self, data):
  130. return WorkspaceAndUserPath(**data)
  131. class CommentsPathSchema(WorkspaceAndContentIdPathSchema):
  132. comment_id = marshmallow.fields.Int(
  133. example=6,
  134. description='id of a valid comment related to content content_id',
  135. required=True,
  136. validate=Range(min=1, error="Value must be greater than 0"),
  137. )
  138. @post_load
  139. def make_path_object(self, data):
  140. return CommentPath(**data)
  141. class FilterContentQuerySchema(marshmallow.Schema):
  142. parent_id = marshmallow.fields.Int(
  143. example=2,
  144. default=0,
  145. description='allow to filter items in a folder.'
  146. ' If not set, then return all contents.'
  147. ' If set to 0, then return root contents.'
  148. ' If set to another value, return all contents'
  149. ' directly included in the folder parent_id',
  150. validate=Range(min=0, error="Value must be positive or 0"),
  151. )
  152. show_archived = marshmallow.fields.Int(
  153. example=0,
  154. default=0,
  155. description='if set to 1, then show archived contents.'
  156. ' Default is 0 - hide archived content',
  157. validate=Range(min=0, max=1, error="Value must be 0 or 1"),
  158. )
  159. show_deleted = marshmallow.fields.Int(
  160. example=0,
  161. default=0,
  162. description='if set to 1, then show deleted contents.'
  163. ' Default is 0 - hide deleted content',
  164. validate=Range(min=0, max=1, error="Value must be 0 or 1"),
  165. )
  166. show_active = marshmallow.fields.Int(
  167. example=1,
  168. default=1,
  169. description='f set to 1, then show active contents. '
  170. 'Default is 1 - show active content.'
  171. ' Note: active content are content '
  172. 'that is neither archived nor deleted. '
  173. 'The reason for this parameter to exist is for example '
  174. 'to allow to show only archived documents',
  175. validate=Range(min=0, max=1, error="Value must be 0 or 1"),
  176. )
  177. content_type = marshmallow.fields.String(
  178. example=ContentType.Any,
  179. default=ContentType.Any,
  180. validate=OneOf(ContentType.allowed_type_values())
  181. )
  182. @post_load
  183. def make_content_filter(self, data):
  184. return ContentFilter(**data)
  185. class ActiveContentFilterQuerySchema(marshmallow.Schema):
  186. limit = marshmallow.fields.Int(
  187. example=2,
  188. default=0,
  189. description='if 0 or not set, return all elements, else return only '
  190. 'the first limit elem (according to offset)',
  191. validate=Range(min=0, error="Value must be positive or 0"),
  192. )
  193. before_datetime = marshmallow.fields.DateTime(
  194. format=DATETIME_FORMAT,
  195. description='return only content lastly updated before this date',
  196. )
  197. @post_load
  198. def make_content_filter(self, data):
  199. return ActiveContentFilter(**data)
  200. class ContentIdsQuerySchema(marshmallow.Schema):
  201. contents_ids = marshmallow.fields.List(
  202. marshmallow.fields.Int(
  203. example=6,
  204. validate=Range(min=1, error="Value must be greater than 0"),
  205. )
  206. )
  207. @post_load
  208. def make_contents_ids(self, data):
  209. return ContentIdsQuery(**data)
  210. ###
  211. class RoleUpdateSchema(marshmallow.Schema):
  212. role = marshmallow.fields.String(
  213. example='contributor',
  214. validate=OneOf(UserRoleInWorkspace.get_all_role_slug())
  215. )
  216. @post_load
  217. def make_role(self, data):
  218. return RoleUpdate(**data)
  219. class WorkspaceMemberInviteSchema(RoleUpdateSchema):
  220. user_id = marshmallow.fields.Int(
  221. example=5,
  222. default=None,
  223. allow_none=True,
  224. )
  225. user_email_or_public_name = marshmallow.fields.String(
  226. example='suri@cate.fr',
  227. default=None,
  228. allow_none=True,
  229. )
  230. @post_load
  231. def make_role(self, data):
  232. return WorkspaceMemberInvitation(**data)
  233. class BasicAuthSchema(marshmallow.Schema):
  234. email = marshmallow.fields.Email(
  235. example='suri.cate@algoo.fr',
  236. required=True
  237. )
  238. password = marshmallow.fields.String(
  239. example='8QLa$<w',
  240. required=True,
  241. load_only=True,
  242. )
  243. class Meta:
  244. description = 'Entry for HTTP Basic Auth'
  245. @post_load
  246. def make_login(self, data):
  247. return LoginCredentials(**data)
  248. class LoginOutputHeaders(marshmallow.Schema):
  249. expire_after = marshmallow.fields.String()
  250. class WorkspaceModifySchema(marshmallow.Schema):
  251. label = marshmallow.fields.String(
  252. example='My Workspace',
  253. )
  254. description = marshmallow.fields.String(
  255. example='A super description of my workspace.',
  256. )
  257. @post_load
  258. def make_workspace_modifications(self, data):
  259. return WorkspaceUpdate(**data)
  260. class WorkspaceCreationSchema(WorkspaceModifySchema):
  261. pass
  262. class NoContentSchema(marshmallow.Schema):
  263. class Meta:
  264. description = 'Empty Schema'
  265. pass
  266. class WorkspaceMenuEntrySchema(marshmallow.Schema):
  267. slug = marshmallow.fields.String(example='markdown-pages')
  268. label = marshmallow.fields.String(example='Markdown Documents')
  269. route = marshmallow.fields.String(
  270. example='/#/workspace/{workspace_id}/contents/?type=mardown-page',
  271. description='the route is the frontend route. '
  272. 'It may include workspace_id '
  273. 'which must be replaced on backend size '
  274. '(the route must be ready-to-use)'
  275. )
  276. fa_icon = marshmallow.fields.String(
  277. example='file-text-o',
  278. description='CSS class of the icon. Example: file-o for using Fontawesome file-text-o icon', # nopep8
  279. )
  280. hexcolor = marshmallow.fields.String(
  281. example='#F0F9DC',
  282. description='Hexadecimal color of the entry.'
  283. )
  284. class Meta:
  285. description = 'Entry element of a workspace menu'
  286. class WorkspaceDigestSchema(marshmallow.Schema):
  287. workspace_id = marshmallow.fields.Int(
  288. example=4,
  289. validate=Range(min=1, error="Value must be greater than 0"),
  290. )
  291. slug = marshmallow.fields.String(example='intranet')
  292. label = marshmallow.fields.String(example='Intranet')
  293. sidebar_entries = marshmallow.fields.Nested(
  294. WorkspaceMenuEntrySchema,
  295. many=True,
  296. )
  297. class Meta:
  298. description = 'Digest of workspace informations'
  299. class WorkspaceSchema(WorkspaceDigestSchema):
  300. description = marshmallow.fields.String(example='All intranet data.')
  301. class Meta:
  302. description = 'Full workspace informations'
  303. class WorkspaceMemberSchema(marshmallow.Schema):
  304. role = marshmallow.fields.String(
  305. example='contributor',
  306. validate=OneOf(UserRoleInWorkspace.get_all_role_slug())
  307. )
  308. user_id = marshmallow.fields.Int(
  309. example=3,
  310. validate=Range(min=1, error="Value must be greater than 0"),
  311. )
  312. workspace_id = marshmallow.fields.Int(
  313. example=4,
  314. validate=Range(min=1, error="Value must be greater than 0"),
  315. )
  316. user = marshmallow.fields.Nested(
  317. UserDigestSchema()
  318. )
  319. workspace = marshmallow.fields.Nested(
  320. WorkspaceDigestSchema(exclude=('sidebar_entries',))
  321. )
  322. is_active = marshmallow.fields.Bool()
  323. class Meta:
  324. description = 'Workspace Member information'
  325. class WorkspaceMemberCreationSchema(WorkspaceMemberSchema):
  326. newly_created = marshmallow.fields.Bool(
  327. exemple=False,
  328. description='Is the user completely new '
  329. '(and account was just created) or not ?',
  330. )
  331. email_sent = marshmallow.fields.Bool(
  332. exemple=False,
  333. description='Has an email been sent to user to inform him about '
  334. 'this new workspace registration and eventually his account'
  335. 'creation'
  336. )
  337. class ApplicationConfigSchema(marshmallow.Schema):
  338. pass
  339. # TODO - G.M - 24-05-2018 - Set this
  340. class ApplicationSchema(marshmallow.Schema):
  341. label = marshmallow.fields.String(example='Calendar')
  342. slug = marshmallow.fields.String(example='calendar')
  343. fa_icon = marshmallow.fields.String(
  344. example='file-o',
  345. description='CSS class of the icon. Example: file-o for using Fontawesome file-o icon', # nopep8
  346. )
  347. hexcolor = marshmallow.fields.String(
  348. example='#FF0000',
  349. description='HTML encoded color associated to the application. Example:#FF0000 for red' # nopep8
  350. )
  351. is_active = marshmallow.fields.Boolean(
  352. example=True,
  353. description='if true, the application is in use in the context',
  354. )
  355. config = marshmallow.fields.Nested(
  356. ApplicationConfigSchema,
  357. )
  358. class Meta:
  359. description = 'Tracim Application informations'
  360. class StatusSchema(marshmallow.Schema):
  361. slug = marshmallow.fields.String(
  362. example='open',
  363. description='the slug represents the type of status. '
  364. 'Statuses are open, closed-validated, closed-invalidated, closed-deprecated' # nopep8
  365. )
  366. global_status = marshmallow.fields.String(
  367. example='open',
  368. description='global_status: open, closed',
  369. validate=OneOf([status.value for status in GlobalStatus]),
  370. )
  371. label = marshmallow.fields.String(example='Open')
  372. fa_icon = marshmallow.fields.String(example='fa-check')
  373. hexcolor = marshmallow.fields.String(example='#0000FF')
  374. class ContentTypeSchema(marshmallow.Schema):
  375. slug = marshmallow.fields.String(
  376. example='pagehtml',
  377. validate=OneOf(ContentType.allowed_types()),
  378. )
  379. fa_icon = marshmallow.fields.String(
  380. example='fa-file-text-o',
  381. description='CSS class of the icon. Example: file-o for using Fontawesome file-o icon', # nopep8
  382. )
  383. hexcolor = marshmallow.fields.String(
  384. example="#FF0000",
  385. description='HTML encoded color associated to the application. Example:#FF0000 for red' # nopep8
  386. )
  387. label = marshmallow.fields.String(
  388. example='Text Documents'
  389. )
  390. creation_label = marshmallow.fields.String(
  391. example='Write a document'
  392. )
  393. available_statuses = marshmallow.fields.Nested(
  394. StatusSchema,
  395. many=True
  396. )
  397. class ContentMoveSchema(marshmallow.Schema):
  398. # TODO - G.M - 30-05-2018 - Read and apply this note
  399. # Note:
  400. # if the new workspace is different, then the backend
  401. # must check if the user is allowed to move to this workspace
  402. # (the user must be content manager of both workspaces)
  403. new_parent_id = marshmallow.fields.Int(
  404. example=42,
  405. description='id of the new parent content id.',
  406. allow_none=True,
  407. required=True,
  408. validate=Range(min=0, error="Value must be positive or 0"),
  409. )
  410. new_workspace_id = marshmallow.fields.Int(
  411. example=2,
  412. description='id of the new workspace id.',
  413. required=True,
  414. validate=Range(min=1, error="Value must be greater than 0"),
  415. )
  416. @post_load
  417. def make_move_params(self, data):
  418. return MoveParams(**data)
  419. class ContentCreationSchema(marshmallow.Schema):
  420. label = marshmallow.fields.String(
  421. example='contract for client XXX',
  422. description='Title of the content to create'
  423. )
  424. content_type = marshmallow.fields.String(
  425. example='html-documents',
  426. validate=OneOf(ContentType.allowed_types_for_folding()), # nopep8
  427. )
  428. parent_id = marshmallow.fields.Integer(
  429. example=35,
  430. description='content_id of parent content, if content should be placed in a folder, this should be folder content_id.'
  431. )
  432. @post_load
  433. def make_content_filter(self, data):
  434. return ContentCreation(**data)
  435. class ContentDigestSchema(marshmallow.Schema):
  436. content_id = marshmallow.fields.Int(
  437. example=6,
  438. validate=Range(min=1, error="Value must be greater than 0"),
  439. )
  440. slug = marshmallow.fields.Str(example='intervention-report-12')
  441. parent_id = marshmallow.fields.Int(
  442. example=34,
  443. allow_none=True,
  444. default=None,
  445. validate=Range(min=0, error="Value must be positive or 0"),
  446. )
  447. workspace_id = marshmallow.fields.Int(
  448. example=19,
  449. validate=Range(min=1, error="Value must be greater than 0"),
  450. )
  451. label = marshmallow.fields.Str(example='Intervention Report 12')
  452. content_type = marshmallow.fields.Str(
  453. example='html-documents',
  454. validate=OneOf(ContentType.allowed_types()),
  455. )
  456. sub_content_types = marshmallow.fields.List(
  457. marshmallow.fields.String(
  458. example='html-content',
  459. validate=OneOf(ContentType.allowed_types())
  460. ),
  461. description='list of content types allowed as sub contents. '
  462. 'This field is required for folder contents, '
  463. 'set it to empty list in other cases'
  464. )
  465. status = marshmallow.fields.Str(
  466. example='closed-deprecated',
  467. validate=OneOf(ContentStatus.allowed_values()),
  468. description='this slug is found in content_type available statuses',
  469. default=open_status
  470. )
  471. is_archived = marshmallow.fields.Bool(example=False, default=False)
  472. is_deleted = marshmallow.fields.Bool(example=False, default=False)
  473. show_in_ui = marshmallow.fields.Bool(
  474. example=True,
  475. description='if false, then do not show content in the treeview. '
  476. 'This may his maybe used for specific contents or '
  477. 'for sub-contents. Default is True. '
  478. 'In first version of the API, this field is always True',
  479. )
  480. class ReadStatusSchema(marshmallow.Schema):
  481. content_id = marshmallow.fields.Int(
  482. example=6,
  483. validate=Range(min=1, error="Value must be greater than 0"),
  484. )
  485. read_by_user = marshmallow.fields.Bool(example=False, default=False)
  486. #####
  487. # Content
  488. #####
  489. class ContentSchema(ContentDigestSchema):
  490. current_revision_id = marshmallow.fields.Int(example=12)
  491. created = marshmallow.fields.DateTime(
  492. format=DATETIME_FORMAT,
  493. description='Content creation date',
  494. )
  495. author = marshmallow.fields.Nested(UserDigestSchema)
  496. modified = marshmallow.fields.DateTime(
  497. format=DATETIME_FORMAT,
  498. description='date of last modification of content',
  499. )
  500. last_modifier = marshmallow.fields.Nested(UserDigestSchema)
  501. class TextBasedDataAbstractSchema(marshmallow.Schema):
  502. raw_content = marshmallow.fields.String(
  503. description='Content of the object, may be raw text or <b>html</b> for example' # nopep8
  504. )
  505. class TextBasedContentSchema(ContentSchema, TextBasedDataAbstractSchema):
  506. pass
  507. #####
  508. # Revision
  509. #####
  510. class RevisionSchema(ContentDigestSchema):
  511. comment_ids = marshmallow.fields.List(
  512. marshmallow.fields.Int(
  513. example=4,
  514. validate=Range(min=1, error="Value must be greater than 0"),
  515. )
  516. )
  517. revision_id = marshmallow.fields.Int(
  518. example=12,
  519. validate=Range(min=1, error="Value must be greater than 0"),
  520. )
  521. revision_type = marshmallow.fields.String(
  522. example=ActionDescription.CREATION,
  523. validate=OneOf(ActionDescription.allowed_values()),
  524. )
  525. created = marshmallow.fields.DateTime(
  526. format=DATETIME_FORMAT,
  527. description='Content creation date',
  528. )
  529. author = marshmallow.fields.Nested(UserDigestSchema)
  530. class TextBasedRevisionSchema(RevisionSchema, TextBasedDataAbstractSchema):
  531. pass
  532. class CommentSchema(marshmallow.Schema):
  533. content_id = marshmallow.fields.Int(
  534. example=6,
  535. validate=Range(min=1, error="Value must be greater than 0"),
  536. )
  537. parent_id = marshmallow.fields.Int(
  538. example=34,
  539. validate=Range(min=0, error="Value must be positive or 0"),
  540. )
  541. raw_content = marshmallow.fields.String(
  542. example='<p>This is just an html comment !</p>'
  543. )
  544. author = marshmallow.fields.Nested(UserDigestSchema)
  545. created = marshmallow.fields.DateTime(
  546. format=DATETIME_FORMAT,
  547. description='comment creation date',
  548. )
  549. class SetCommentSchema(marshmallow.Schema):
  550. raw_content = marshmallow.fields.String(
  551. example='<p>This is just an html comment !</p>'
  552. )
  553. @post_load()
  554. def create_comment(self, data):
  555. return CommentCreation(**data)
  556. class ContentModifyAbstractSchema(marshmallow.Schema):
  557. label = marshmallow.fields.String(
  558. required=True,
  559. example='contract for client XXX',
  560. description='New title of the content'
  561. )
  562. class TextBasedContentModifySchema(ContentModifyAbstractSchema, TextBasedDataAbstractSchema): # nopep8
  563. @post_load
  564. def text_based_content_update(self, data):
  565. return TextBasedContentUpdate(**data)
  566. class SetContentStatusSchema(marshmallow.Schema):
  567. status = marshmallow.fields.Str(
  568. example='closed-deprecated',
  569. validate=OneOf(ContentStatus.allowed_values()),
  570. description='this slug is found in content_type available statuses',
  571. default=open_status,
  572. required=True,
  573. )
  574. @post_load
  575. def set_status(self, data):
  576. return SetContentStatus(**data)