schemas.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. # coding=utf-8
  2. import marshmallow
  3. from marshmallow import post_load
  4. from marshmallow.validate import OneOf
  5. from tracim.lib.utils.utils import DATETIME_FORMAT
  6. from tracim.models.auth import Profile
  7. from tracim.models.contents import CONTENT_DEFAULT_TYPE
  8. from tracim.models.contents import CONTENT_DEFAULT_STATUS
  9. from tracim.models.contents import GlobalStatus
  10. from tracim.models.contents import open_status
  11. from tracim.models.context_models import ContentCreation
  12. from tracim.models.context_models import SetContentStatus
  13. from tracim.models.context_models import CommentCreation
  14. from tracim.models.context_models import CommentPath
  15. from tracim.models.context_models import MoveParams
  16. from tracim.models.context_models import WorkspaceAndContentPath
  17. from tracim.models.context_models import ContentFilter
  18. from tracim.models.context_models import LoginCredentials
  19. from tracim.models.context_models import HTMLDocumentUpdate
  20. from tracim.models.context_models import ThreadUpdate
  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(example=3, required=True)
  75. class WorkspaceIdPathSchema(marshmallow.Schema):
  76. workspace_id = marshmallow.fields.Int(example=4, required=True)
  77. class ContentIdPathSchema(marshmallow.Schema):
  78. content_id = marshmallow.fields.Int(example=6, required=True)
  79. class WorkspaceAndContentIdPathSchema(
  80. WorkspaceIdPathSchema,
  81. ContentIdPathSchema
  82. ):
  83. @post_load
  84. def make_path_object(self, data):
  85. return WorkspaceAndContentPath(**data)
  86. class CommentsPathSchema(WorkspaceAndContentIdPathSchema):
  87. comment_id = marshmallow.fields.Int(
  88. example=6,
  89. description='id of a comment related to content content_id',
  90. required=True
  91. )
  92. @post_load
  93. def make_path_object(self, data):
  94. return CommentPath(**data)
  95. class FilterContentQuerySchema(marshmallow.Schema):
  96. parent_id = marshmallow.fields.Int(
  97. example=2,
  98. default=0,
  99. description='allow to filter items in a folder.'
  100. ' If not set, then return all contents.'
  101. ' If set to 0, then return root contents.'
  102. ' If set to another value, return all contents'
  103. ' directly included in the folder parent_id'
  104. )
  105. show_archived = marshmallow.fields.Int(
  106. example=0,
  107. default=0,
  108. description='if set to 1, then show archived contents.'
  109. ' Default is 0 - hide archived content'
  110. )
  111. show_deleted = marshmallow.fields.Int(
  112. example=0,
  113. default=0,
  114. description='if set to 1, then show deleted contents.'
  115. ' Default is 0 - hide deleted content'
  116. )
  117. show_active = marshmallow.fields.Int(
  118. example=1,
  119. default=1,
  120. description='f set to 1, then show active contents. '
  121. 'Default is 1 - show active content.'
  122. ' Note: active content are content '
  123. 'that is neither archived nor deleted. '
  124. 'The reason for this parameter to exist is for example '
  125. 'to allow to show only archived documents'
  126. )
  127. @post_load
  128. def make_content_filter(self, data):
  129. return ContentFilter(**data)
  130. ###
  131. class BasicAuthSchema(marshmallow.Schema):
  132. email = marshmallow.fields.Email(
  133. example='suri.cate@algoo.fr',
  134. required=True
  135. )
  136. password = marshmallow.fields.String(
  137. example='8QLa$<w',
  138. required=True,
  139. load_only=True,
  140. )
  141. class Meta:
  142. description = 'Entry for HTTP Basic Auth'
  143. @post_load
  144. def make_login(self, data):
  145. return LoginCredentials(**data)
  146. class LoginOutputHeaders(marshmallow.Schema):
  147. expire_after = marshmallow.fields.String()
  148. class NoContentSchema(marshmallow.Schema):
  149. class Meta:
  150. description = 'Empty Schema'
  151. pass
  152. class WorkspaceMenuEntrySchema(marshmallow.Schema):
  153. slug = marshmallow.fields.String(example='markdown-pages')
  154. label = marshmallow.fields.String(example='Markdown Documents')
  155. route = marshmallow.fields.String(
  156. example='/#/workspace/{workspace_id}/contents/?type=mardown-page',
  157. description='the route is the frontend route. '
  158. 'It may include workspace_id '
  159. 'which must be replaced on backend size '
  160. '(the route must be ready-to-use)'
  161. )
  162. fa_icon = marshmallow.fields.String(
  163. example='file-text-o',
  164. description='CSS class of the icon. Example: file-o for using Fontawesome file-text-o icon', # nopep8
  165. )
  166. hexcolor = marshmallow.fields.String(
  167. example='#F0F9DC',
  168. description='Hexadecimal color of the entry.'
  169. )
  170. class Meta:
  171. description = 'Entry element of a workspace menu'
  172. class WorkspaceDigestSchema(marshmallow.Schema):
  173. workspace_id = marshmallow.fields.Int(example=4)
  174. slug = marshmallow.fields.String(example='intranet')
  175. label = marshmallow.fields.String(example='Intranet')
  176. sidebar_entries = marshmallow.fields.Nested(
  177. WorkspaceMenuEntrySchema,
  178. many=True,
  179. )
  180. class Meta:
  181. description = 'Digest of workspace informations'
  182. class WorkspaceSchema(WorkspaceDigestSchema):
  183. description = marshmallow.fields.String(example='All intranet data.')
  184. class Meta:
  185. description = 'Full workspace informations'
  186. class WorkspaceMemberSchema(marshmallow.Schema):
  187. role = marshmallow.fields.String(
  188. example='contributor',
  189. validate=OneOf(UserRoleInWorkspace.get_all_role_slug())
  190. )
  191. user_id = marshmallow.fields.Int(example=3)
  192. workspace_id = marshmallow.fields.Int(example=4)
  193. user = marshmallow.fields.Nested(
  194. UserSchema(only=('public_name', 'avatar_url'))
  195. )
  196. class Meta:
  197. description = 'Workspace Member information'
  198. class ApplicationConfigSchema(marshmallow.Schema):
  199. pass
  200. # TODO - G.M - 24-05-2018 - Set this
  201. class ApplicationSchema(marshmallow.Schema):
  202. label = marshmallow.fields.String(example='Calendar')
  203. slug = marshmallow.fields.String(example='calendar')
  204. fa_icon = marshmallow.fields.String(
  205. example='file-o',
  206. description='CSS class of the icon. Example: file-o for using Fontawesome file-o icon', # nopep8
  207. )
  208. hexcolor = marshmallow.fields.String(
  209. example='#FF0000',
  210. description='HTML encoded color associated to the application. Example:#FF0000 for red' # nopep8
  211. )
  212. is_active = marshmallow.fields.Boolean(
  213. example=True,
  214. description='if true, the application is in use in the context',
  215. )
  216. config = marshmallow.fields.Nested(
  217. ApplicationConfigSchema,
  218. )
  219. class Meta:
  220. description = 'Tracim Application informations'
  221. class StatusSchema(marshmallow.Schema):
  222. slug = marshmallow.fields.String(
  223. example='open',
  224. description='the slug represents the type of status. '
  225. 'Statuses are open, closed-validated, closed-invalidated, closed-deprecated' # nopep8
  226. )
  227. global_status = marshmallow.fields.String(
  228. example='open',
  229. description='global_status: open, closed',
  230. validate=OneOf([status.value for status in GlobalStatus]),
  231. )
  232. label = marshmallow.fields.String(example='Open')
  233. fa_icon = marshmallow.fields.String(example='fa-check')
  234. hexcolor = marshmallow.fields.String(example='#0000FF')
  235. class ContentTypeSchema(marshmallow.Schema):
  236. slug = marshmallow.fields.String(
  237. example='pagehtml',
  238. validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
  239. )
  240. fa_icon = marshmallow.fields.String(
  241. example='fa-file-text-o',
  242. description='CSS class of the icon. Example: file-o for using Fontawesome file-o icon', # nopep8
  243. )
  244. hexcolor = marshmallow.fields.String(
  245. example="#FF0000",
  246. description='HTML encoded color associated to the application. Example:#FF0000 for red' # nopep8
  247. )
  248. label = marshmallow.fields.String(
  249. example='Text Documents'
  250. )
  251. creation_label = marshmallow.fields.String(
  252. example='Write a document'
  253. )
  254. available_statuses = marshmallow.fields.Nested(
  255. StatusSchema,
  256. many=True
  257. )
  258. class ContentMoveSchema(marshmallow.Schema):
  259. # TODO - G.M - 30-05-2018 - Read and apply this note
  260. # Note:
  261. # if the new workspace is different, then the backend
  262. # must check if the user is allowed to move to this workspace
  263. # (the user must be content manager of both workspaces)
  264. new_parent_id = marshmallow.fields.Int(
  265. example=42,
  266. description='id of the new parent content id.',
  267. allow_none=True,
  268. required=True,
  269. )
  270. new_workspace_id = marshmallow.fields.Int(
  271. example=2,
  272. description='id of the new workspace id.',
  273. required=True
  274. )
  275. @post_load
  276. def make_move_params(self, data):
  277. return MoveParams(**data)
  278. class ContentCreationSchema(marshmallow.Schema):
  279. label = marshmallow.fields.String(
  280. example='contract for client XXX',
  281. description='Title of the content to create'
  282. )
  283. content_type = marshmallow.fields.String(
  284. example='html-documents',
  285. validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
  286. )
  287. @post_load
  288. def make_content_filter(self, data):
  289. return ContentCreation(**data)
  290. class ContentDigestSchema(marshmallow.Schema):
  291. content_id = marshmallow.fields.Int(example=6)
  292. slug = marshmallow.fields.Str(example='intervention-report-12')
  293. parent_id = marshmallow.fields.Int(
  294. example=34,
  295. allow_none=True,
  296. default=None
  297. )
  298. workspace_id = marshmallow.fields.Int(
  299. example=19,
  300. )
  301. label = marshmallow.fields.Str(example='Intervention Report 12')
  302. content_type = marshmallow.fields.Str(
  303. example='html-documents',
  304. validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
  305. )
  306. sub_content_types = marshmallow.fields.List(
  307. marshmallow.fields.String(),
  308. description='list of content types allowed as sub contents. '
  309. 'This field is required for folder contents, '
  310. 'set it to empty list in other cases'
  311. )
  312. status = marshmallow.fields.Str(
  313. example='closed-deprecated',
  314. validate=OneOf([status.slug for status in CONTENT_DEFAULT_STATUS]),
  315. description='this slug is found in content_type available statuses',
  316. default=open_status
  317. )
  318. is_archived = marshmallow.fields.Bool(example=False, default=False)
  319. is_deleted = marshmallow.fields.Bool(example=False, default=False)
  320. show_in_ui = marshmallow.fields.Bool(
  321. example=True,
  322. description='if false, then do not show content in the treeview. '
  323. 'This may his maybe used for specific contents or '
  324. 'for sub-contents. Default is True. '
  325. 'In first version of the API, this field is always True',
  326. )
  327. #####
  328. # Content
  329. #####
  330. class ContentSchema(ContentDigestSchema):
  331. current_revision_id = marshmallow.fields.Int(example=12)
  332. created = marshmallow.fields.DateTime(
  333. format=DATETIME_FORMAT,
  334. description='Content creation date',
  335. )
  336. author = marshmallow.fields.Nested(UserDigestSchema)
  337. modified = marshmallow.fields.DateTime(
  338. format=DATETIME_FORMAT,
  339. description='date of last modification of content',
  340. )
  341. last_modifier = marshmallow.fields.Nested(UserDigestSchema)
  342. class ThreadContentSchema(ContentSchema):
  343. content_type = marshmallow.fields.Str(
  344. example='thread',
  345. validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
  346. )
  347. raw_content = marshmallow.fields.String('Description of Thread')
  348. class HtmlDocumentContentSchema(ContentSchema):
  349. content_type = marshmallow.fields.Str(
  350. example='html-documents',
  351. validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
  352. )
  353. raw_content = marshmallow.fields.String('<p>Html page Content!</p>')
  354. #####
  355. # Revision
  356. #####
  357. class RevisionSchema(ContentDigestSchema):
  358. comment_ids = marshmallow.fields.List(marshmallow.fields.Int(example=4))
  359. revision_id = marshmallow.fields.Int(example=12)
  360. created = marshmallow.fields.DateTime(
  361. format=DATETIME_FORMAT,
  362. description='Content creation date',
  363. )
  364. author = marshmallow.fields.Nested(UserDigestSchema)
  365. class ThreadRevisionSchema(RevisionSchema):
  366. content_type = marshmallow.fields.Str(
  367. example='thread',
  368. validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
  369. )
  370. raw_content = marshmallow.fields.String('Description of Thread')
  371. class HtmlDocumentRevisionSchema(RevisionSchema):
  372. content_type = marshmallow.fields.Str(
  373. example='html-documents',
  374. validate=OneOf([content.slug for content in CONTENT_DEFAULT_TYPE]),
  375. )
  376. raw_content = marshmallow.fields.String('<p>Html page Content!</p>')
  377. ####
  378. class CommentSchema(marshmallow.Schema):
  379. content_id = marshmallow.fields.Int(example=6)
  380. parent_id = marshmallow.fields.Int(example=34)
  381. raw_content = marshmallow.fields.String(
  382. example='<p>This is just an html comment !</p>'
  383. )
  384. author = marshmallow.fields.Nested(UserDigestSchema)
  385. created = marshmallow.fields.DateTime(
  386. format=DATETIME_FORMAT,
  387. description='comment creation date',
  388. )
  389. class ContentModifySchema(marshmallow.Schema):
  390. label = marshmallow.fields.String(
  391. example='contract for client XXX',
  392. description='New title of the content'
  393. )
  394. class HtmlDocumentModifySchema(ContentModifySchema):
  395. raw_content = marshmallow.fields.String('<p>Html page Content!</p>')
  396. @post_load
  397. def html_document_update(self, data):
  398. return HTMLDocumentUpdate(**data)
  399. class ThreadModifySchema(ContentModifySchema):
  400. raw_content = marshmallow.fields.String('Description of Thread')
  401. @post_load
  402. def thread_update(self, data):
  403. return ThreadUpdate(**data)
  404. class SetCommentSchema(marshmallow.Schema):
  405. raw_content = marshmallow.fields.String(
  406. example='<p>This is just an html comment !</p>'
  407. )
  408. @post_load
  409. def create_comment(self, data):
  410. return CommentCreation(**data)
  411. class SetContentStatusSchema(marshmallow.Schema):
  412. status = marshmallow.fields.Str(
  413. example='closed-deprecated',
  414. validate=OneOf([status.slug for status in CONTENT_DEFAULT_STATUS]),
  415. description='this slug is found in content_type available statuses',
  416. default=open_status,
  417. required=True,
  418. )
  419. @post_load
  420. def set_status(self, data):
  421. return SetContentStatus(**data)