schemas.py 19KB

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