schemas.py 19KB

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