schemas.py 18KB

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