schemas.py 19KB

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