schemas.py 15KB

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