schemas.py 17KB

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