schemas.py 16KB

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