schemas.py 19KB

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