schemas.py 17KB

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