schemas.py 19KB

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