schemas.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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 WorkspaceMemberInvitation
  13. from tracim.models.context_models import WorkspaceUpdate
  14. from tracim.models.context_models import RoleUpdate
  15. from tracim.models.context_models import CommentCreation
  16. from tracim.models.context_models import TextBasedContentUpdate
  17. from tracim.models.context_models import SetContentStatus
  18. from tracim.models.context_models import CommentPath
  19. from tracim.models.context_models import MoveParams
  20. from tracim.models.context_models import WorkspaceAndContentPath
  21. from tracim.models.context_models import WorkspaceAndUserPath
  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(example=3, required=True)
  78. class WorkspaceIdPathSchema(marshmallow.Schema):
  79. workspace_id = marshmallow.fields.Int(example=4, required=True)
  80. class ContentIdPathSchema(marshmallow.Schema):
  81. content_id = marshmallow.fields.Int(example=6, required=True)
  82. class WorkspaceAndUserIdPathSchema(
  83. UserIdPathSchema,
  84. WorkspaceIdPathSchema
  85. ):
  86. @post_load
  87. def make_path_object(self, data):
  88. return WorkspaceAndUserPath(**data)
  89. class WorkspaceAndContentIdPathSchema(
  90. WorkspaceIdPathSchema,
  91. ContentIdPathSchema
  92. ):
  93. @post_load
  94. def make_path_object(self, data):
  95. return WorkspaceAndContentPath(**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 RoleUpdateSchema(marshmallow.Schema):
  142. role = marshmallow.fields.String(
  143. example='contributor',
  144. validate=OneOf(UserRoleInWorkspace.get_all_role_slug())
  145. )
  146. @post_load
  147. def make_role(self, data):
  148. return RoleUpdate(**data)
  149. class WorkspaceMemberInviteSchema(RoleUpdateSchema):
  150. user_id = marshmallow.fields.Int(
  151. example=5,
  152. default=None,
  153. allow_none=True,
  154. )
  155. user_email_or_public_name = marshmallow.fields.String(
  156. example='suri@cate.fr',
  157. default=None,
  158. allow_none=True,
  159. )
  160. @post_load
  161. def make_role(self, data):
  162. return WorkspaceMemberInvitation(**data)
  163. class BasicAuthSchema(marshmallow.Schema):
  164. email = marshmallow.fields.Email(
  165. example='suri.cate@algoo.fr',
  166. required=True
  167. )
  168. password = marshmallow.fields.String(
  169. example='8QLa$<w',
  170. required=True,
  171. load_only=True,
  172. )
  173. class Meta:
  174. description = 'Entry for HTTP Basic Auth'
  175. @post_load
  176. def make_login(self, data):
  177. return LoginCredentials(**data)
  178. class LoginOutputHeaders(marshmallow.Schema):
  179. expire_after = marshmallow.fields.String()
  180. class WorkspaceModifySchema(marshmallow.Schema):
  181. label = marshmallow.fields.String(
  182. example='My Workspace',
  183. )
  184. description = marshmallow.fields.String(
  185. example='A super description of my workspace.',
  186. )
  187. @post_load
  188. def make_workspace_modifications(self, data):
  189. return WorkspaceUpdate(**data)
  190. class WorkspaceCreationSchema(WorkspaceModifySchema):
  191. pass
  192. class NoContentSchema(marshmallow.Schema):
  193. class Meta:
  194. description = 'Empty Schema'
  195. pass
  196. class WorkspaceMenuEntrySchema(marshmallow.Schema):
  197. slug = marshmallow.fields.String(example='markdown-pages')
  198. label = marshmallow.fields.String(example='Markdown Documents')
  199. route = marshmallow.fields.String(
  200. example='/#/workspace/{workspace_id}/contents/?type=mardown-page',
  201. description='the route is the frontend route. '
  202. 'It may include workspace_id '
  203. 'which must be replaced on backend size '
  204. '(the route must be ready-to-use)'
  205. )
  206. fa_icon = marshmallow.fields.String(
  207. example='file-text-o',
  208. description='CSS class of the icon. Example: file-o for using Fontawesome file-text-o icon', # nopep8
  209. )
  210. hexcolor = marshmallow.fields.String(
  211. example='#F0F9DC',
  212. description='Hexadecimal color of the entry.'
  213. )
  214. class Meta:
  215. description = 'Entry element of a workspace menu'
  216. class WorkspaceDigestSchema(marshmallow.Schema):
  217. workspace_id = marshmallow.fields.Int(example=4)
  218. slug = marshmallow.fields.String(example='intranet')
  219. label = marshmallow.fields.String(example='Intranet')
  220. sidebar_entries = marshmallow.fields.Nested(
  221. WorkspaceMenuEntrySchema,
  222. many=True,
  223. )
  224. class Meta:
  225. description = 'Digest of workspace informations'
  226. class WorkspaceSchema(WorkspaceDigestSchema):
  227. description = marshmallow.fields.String(example='All intranet data.')
  228. class Meta:
  229. description = 'Full workspace informations'
  230. class WorkspaceMemberSchema(marshmallow.Schema):
  231. role = marshmallow.fields.String(
  232. example='contributor',
  233. validate=OneOf(UserRoleInWorkspace.get_all_role_slug())
  234. )
  235. user_id = marshmallow.fields.Int(example=3)
  236. workspace_id = marshmallow.fields.Int(example=4)
  237. user = marshmallow.fields.Nested(
  238. UserDigestSchema()
  239. )
  240. workspace = marshmallow.fields.Nested(
  241. WorkspaceDigestSchema(exclude=('sidebar_entries',))
  242. )
  243. is_active = marshmallow.fields.Bool()
  244. class Meta:
  245. description = 'Workspace Member information'
  246. class WorkspaceMemberCreationSchema(WorkspaceMemberSchema):
  247. newly_created = marshmallow.fields.Bool(
  248. exemple=False,
  249. description='Is the user completely new '
  250. '(and account was just created) or not ?',
  251. )
  252. email_sent = marshmallow.fields.Bool(
  253. exemple=False,
  254. description='Has an email been sent to user to inform him about '
  255. 'this new workspace registration and eventually his account'
  256. 'creation'
  257. )
  258. class ApplicationConfigSchema(marshmallow.Schema):
  259. pass
  260. # TODO - G.M - 24-05-2018 - Set this
  261. class ApplicationSchema(marshmallow.Schema):
  262. label = marshmallow.fields.String(example='Calendar')
  263. slug = marshmallow.fields.String(example='calendar')
  264. fa_icon = marshmallow.fields.String(
  265. example='file-o',
  266. description='CSS class of the icon. Example: file-o for using Fontawesome file-o icon', # nopep8
  267. )
  268. hexcolor = marshmallow.fields.String(
  269. example='#FF0000',
  270. description='HTML encoded color associated to the application. Example:#FF0000 for red' # nopep8
  271. )
  272. is_active = marshmallow.fields.Boolean(
  273. example=True,
  274. description='if true, the application is in use in the context',
  275. )
  276. config = marshmallow.fields.Nested(
  277. ApplicationConfigSchema,
  278. )
  279. class Meta:
  280. description = 'Tracim Application informations'
  281. class StatusSchema(marshmallow.Schema):
  282. slug = marshmallow.fields.String(
  283. example='open',
  284. description='the slug represents the type of status. '
  285. 'Statuses are open, closed-validated, closed-invalidated, closed-deprecated' # nopep8
  286. )
  287. global_status = marshmallow.fields.String(
  288. example='open',
  289. description='global_status: open, closed',
  290. validate=OneOf([status.value for status in GlobalStatus]),
  291. )
  292. label = marshmallow.fields.String(example='Open')
  293. fa_icon = marshmallow.fields.String(example='fa-check')
  294. hexcolor = marshmallow.fields.String(example='#0000FF')
  295. class ContentTypeSchema(marshmallow.Schema):
  296. slug = marshmallow.fields.String(
  297. example='pagehtml',
  298. validate=OneOf(ContentType.allowed_types()),
  299. )
  300. fa_icon = marshmallow.fields.String(
  301. example='fa-file-text-o',
  302. description='CSS class of the icon. Example: file-o for using Fontawesome file-o icon', # nopep8
  303. )
  304. hexcolor = marshmallow.fields.String(
  305. example="#FF0000",
  306. description='HTML encoded color associated to the application. Example:#FF0000 for red' # nopep8
  307. )
  308. label = marshmallow.fields.String(
  309. example='Text Documents'
  310. )
  311. creation_label = marshmallow.fields.String(
  312. example='Write a document'
  313. )
  314. available_statuses = marshmallow.fields.Nested(
  315. StatusSchema,
  316. many=True
  317. )
  318. class ContentMoveSchema(marshmallow.Schema):
  319. # TODO - G.M - 30-05-2018 - Read and apply this note
  320. # Note:
  321. # if the new workspace is different, then the backend
  322. # must check if the user is allowed to move to this workspace
  323. # (the user must be content manager of both workspaces)
  324. new_parent_id = marshmallow.fields.Int(
  325. example=42,
  326. description='id of the new parent content id.',
  327. allow_none=True,
  328. required=True,
  329. )
  330. new_workspace_id = marshmallow.fields.Int(
  331. example=2,
  332. description='id of the new workspace id.',
  333. required=True
  334. )
  335. @post_load
  336. def make_move_params(self, data):
  337. return MoveParams(**data)
  338. class ContentCreationSchema(marshmallow.Schema):
  339. label = marshmallow.fields.String(
  340. example='contract for client XXX',
  341. description='Title of the content to create'
  342. )
  343. content_type = marshmallow.fields.String(
  344. example='html-documents',
  345. validate=OneOf(ContentType.allowed_types_for_folding()), # nopep8
  346. )
  347. @post_load
  348. def make_content_filter(self, data):
  349. return ContentCreation(**data)
  350. class ContentDigestSchema(marshmallow.Schema):
  351. content_id = marshmallow.fields.Int(example=6)
  352. slug = marshmallow.fields.Str(example='intervention-report-12')
  353. parent_id = marshmallow.fields.Int(
  354. example=34,
  355. allow_none=True,
  356. default=None
  357. )
  358. workspace_id = marshmallow.fields.Int(
  359. example=19,
  360. )
  361. label = marshmallow.fields.Str(example='Intervention Report 12')
  362. content_type = marshmallow.fields.Str(
  363. example='html-documents',
  364. validate=OneOf(ContentType.allowed_types()),
  365. )
  366. sub_content_types = marshmallow.fields.List(
  367. marshmallow.fields.String(
  368. example='html-content',
  369. validate=OneOf(ContentType.allowed_types())
  370. ),
  371. description='list of content types allowed as sub contents. '
  372. 'This field is required for folder contents, '
  373. 'set it to empty list in other cases'
  374. )
  375. status = marshmallow.fields.Str(
  376. example='closed-deprecated',
  377. validate=OneOf(ContentStatus.allowed_values()),
  378. description='this slug is found in content_type available statuses',
  379. default=open_status
  380. )
  381. is_archived = marshmallow.fields.Bool(example=False, default=False)
  382. is_deleted = marshmallow.fields.Bool(example=False, default=False)
  383. show_in_ui = marshmallow.fields.Bool(
  384. example=True,
  385. description='if false, then do not show content in the treeview. '
  386. 'This may his maybe used for specific contents or '
  387. 'for sub-contents. Default is True. '
  388. 'In first version of the API, this field is always True',
  389. )
  390. #####
  391. # Content
  392. #####
  393. class ContentSchema(ContentDigestSchema):
  394. current_revision_id = marshmallow.fields.Int(example=12)
  395. created = marshmallow.fields.DateTime(
  396. format=DATETIME_FORMAT,
  397. description='Content creation date',
  398. )
  399. author = marshmallow.fields.Nested(UserDigestSchema)
  400. modified = marshmallow.fields.DateTime(
  401. format=DATETIME_FORMAT,
  402. description='date of last modification of content',
  403. )
  404. last_modifier = marshmallow.fields.Nested(UserDigestSchema)
  405. class TextBasedDataAbstractSchema(marshmallow.Schema):
  406. raw_content = marshmallow.fields.String(
  407. description='Content of the object, may be raw text or <b>html</b> for example' # nopep8
  408. )
  409. class TextBasedContentSchema(ContentSchema, TextBasedDataAbstractSchema):
  410. pass
  411. #####
  412. # Revision
  413. #####
  414. class RevisionSchema(ContentDigestSchema):
  415. comment_ids = marshmallow.fields.List(marshmallow.fields.Int(example=4))
  416. revision_id = marshmallow.fields.Int(example=12)
  417. created = marshmallow.fields.DateTime(
  418. format=DATETIME_FORMAT,
  419. description='Content creation date',
  420. )
  421. author = marshmallow.fields.Nested(UserDigestSchema)
  422. class TextBasedRevisionSchema(RevisionSchema, TextBasedDataAbstractSchema):
  423. pass
  424. class CommentSchema(marshmallow.Schema):
  425. content_id = marshmallow.fields.Int(example=6)
  426. parent_id = marshmallow.fields.Int(example=34)
  427. raw_content = marshmallow.fields.String(
  428. example='<p>This is just an html comment !</p>'
  429. )
  430. author = marshmallow.fields.Nested(UserDigestSchema)
  431. created = marshmallow.fields.DateTime(
  432. format=DATETIME_FORMAT,
  433. description='comment creation date',
  434. )
  435. class SetCommentSchema(marshmallow.Schema):
  436. raw_content = marshmallow.fields.String(
  437. example='<p>This is just an html comment !</p>'
  438. )
  439. @post_load()
  440. def create_comment(self, data):
  441. return CommentCreation(**data)
  442. class ContentModifyAbstractSchema(marshmallow.Schema):
  443. label = marshmallow.fields.String(
  444. required=True,
  445. example='contract for client XXX',
  446. description='New title of the content'
  447. )
  448. class TextBasedContentModifySchema(ContentModifyAbstractSchema, TextBasedDataAbstractSchema): # nopep8
  449. @post_load
  450. def text_based_content_update(self, data):
  451. return TextBasedContentUpdate(**data)
  452. class SetContentStatusSchema(marshmallow.Schema):
  453. status = marshmallow.fields.Str(
  454. example='closed-deprecated',
  455. validate=OneOf(ContentStatus.allowed_values()),
  456. description='this slug is found in content_type available statuses',
  457. default=open_status,
  458. required=True,
  459. )
  460. @post_load
  461. def set_status(self, data):
  462. return SetContentStatus(**data)