schemas.py 20KB

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