schemas.py 19KB

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