file_controller.py 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. # coding=utf-8
  2. import typing
  3. import transaction
  4. from depot.manager import DepotManager
  5. from preview_generator.exception import UnavailablePreviewType
  6. from pyramid.config import Configurator
  7. from pyramid.response import FileResponse, FileIter
  8. try: # Python 3.5+
  9. from http import HTTPStatus
  10. except ImportError:
  11. from http import client as HTTPStatus
  12. from tracim import TracimRequest
  13. from tracim.extensions import hapic
  14. from tracim.lib.core.content import ContentApi
  15. from tracim.views.controllers import Controller
  16. from tracim.views.core_api.schemas import FileContentSchema
  17. from tracim.views.core_api.schemas import AllowedJpgPreviewDimSchema
  18. from tracim.views.core_api.schemas import ContentPreviewSizedPathSchema
  19. from tracim.views.core_api.schemas import RevisionPreviewSizedPathSchema
  20. from tracim.views.core_api.schemas import PageQuerySchema
  21. from tracim.views.core_api.schemas import WorkspaceAndContentRevisionIdPathSchema # nopep8
  22. from tracim.views.core_api.schemas import FileRevisionSchema
  23. from tracim.views.core_api.schemas import SetContentStatusSchema
  24. from tracim.views.core_api.schemas import FileContentModifySchema
  25. from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
  26. from tracim.views.core_api.schemas import NoContentSchema
  27. from tracim.lib.utils.authorization import require_content_types
  28. from tracim.lib.utils.authorization import require_workspace_role
  29. from tracim.models.data import UserRoleInWorkspace
  30. from tracim.models.context_models import ContentInContext
  31. from tracim.models.context_models import RevisionInContext
  32. from tracim.models.contents import ContentTypeLegacy as ContentType
  33. from tracim.models.contents import file_type
  34. from tracim.models.revision_protection import new_revision
  35. from tracim.exceptions import EmptyLabelNotAllowed
  36. from tracim.exceptions import PreviewDimNotAllowed
  37. FILE_ENDPOINTS_TAG = 'Files'
  38. class FileController(Controller):
  39. # File data
  40. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  41. @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
  42. @require_content_types([file_type])
  43. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  44. #@hapic.input_files()
  45. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  46. def upload_file(self, context, request: TracimRequest, hapic_data=None):
  47. app_config = request.registry.settings['CFG']
  48. api = ContentApi(
  49. current_user=request.current_user,
  50. session=request.dbsession,
  51. config=app_config,
  52. )
  53. content = api.get_one(
  54. hapic_data.path.content_id,
  55. content_type=ContentType.Any
  56. )
  57. file = request.POST['files']
  58. with new_revision(
  59. session=request.dbsession,
  60. tm=transaction.manager,
  61. content=content
  62. ):
  63. api.update_file_data(
  64. content,
  65. new_filename=file.filename,
  66. new_mimetype=file.type,
  67. new_content=file.file,
  68. )
  69. return
  70. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  71. @require_workspace_role(UserRoleInWorkspace.READER)
  72. @require_content_types([file_type])
  73. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  74. @hapic.output_file([])
  75. def download_file(self, context, request: TracimRequest, hapic_data=None):
  76. app_config = request.registry.settings['CFG']
  77. api = ContentApi(
  78. current_user=request.current_user,
  79. session=request.dbsession,
  80. config=app_config,
  81. )
  82. content = api.get_one(
  83. hapic_data.path.content_id,
  84. content_type=ContentType.Any
  85. )
  86. file = DepotManager.get().get(content.depot_file)
  87. response = request.response
  88. response.content_type = file.content_type
  89. response.app_iter = FileIter(file)
  90. return response
  91. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  92. @require_workspace_role(UserRoleInWorkspace.READER)
  93. @require_content_types([file_type])
  94. @hapic.input_path(WorkspaceAndContentRevisionIdPathSchema())
  95. @hapic.output_file([])
  96. def download_revisions_file(self, context, request: TracimRequest, hapic_data=None): # nopep8
  97. app_config = request.registry.settings['CFG']
  98. api = ContentApi(
  99. current_user=request.current_user,
  100. session=request.dbsession,
  101. config=app_config,
  102. )
  103. content = api.get_one(
  104. hapic_data.path.content_id,
  105. content_type=ContentType.Any
  106. )
  107. revision = api.get_one_revision(
  108. revision_id=hapic_data.path.revision_id,
  109. content=content
  110. )
  111. file = DepotManager.get().get(revision.depot_file)
  112. response = request.response
  113. response.content_type = file.content_type
  114. response.app_iter = FileIter(file)
  115. return response
  116. # preview
  117. # pdf
  118. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  119. @require_workspace_role(UserRoleInWorkspace.READER)
  120. @require_content_types([file_type])
  121. @hapic.handle_exception(UnavailablePreviewType, HTTPStatus.BAD_REQUEST)
  122. @hapic.input_query(PageQuerySchema())
  123. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  124. @hapic.output_file([])
  125. def preview_pdf(self, context, request: TracimRequest, hapic_data=None):
  126. app_config = request.registry.settings['CFG']
  127. api = ContentApi(
  128. current_user=request.current_user,
  129. session=request.dbsession,
  130. config=app_config,
  131. )
  132. content = api.get_one(
  133. hapic_data.path.content_id,
  134. content_type=ContentType.Any
  135. )
  136. pdf_preview_path = api.get_pdf_preview_path(
  137. content.content_id,
  138. content.revision_id,
  139. page=hapic_data.query.page
  140. )
  141. return FileResponse(pdf_preview_path)
  142. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  143. @require_workspace_role(UserRoleInWorkspace.READER)
  144. @require_content_types([file_type])
  145. @hapic.handle_exception(UnavailablePreviewType, HTTPStatus.BAD_REQUEST)
  146. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  147. @hapic.output_file([])
  148. def preview_pdf_full(self, context, request: TracimRequest, hapic_data=None):
  149. app_config = request.registry.settings['CFG']
  150. api = ContentApi(
  151. current_user=request.current_user,
  152. session=request.dbsession,
  153. config=app_config,
  154. )
  155. content = api.get_one(
  156. hapic_data.path.content_id,
  157. content_type=ContentType.Any
  158. )
  159. pdf_preview_path = api.get_full_pdf_preview_path(content.revision_id)
  160. return FileResponse(pdf_preview_path)
  161. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  162. @require_workspace_role(UserRoleInWorkspace.READER)
  163. @require_content_types([file_type])
  164. @hapic.handle_exception(UnavailablePreviewType, HTTPStatus.BAD_REQUEST)
  165. @hapic.input_path(WorkspaceAndContentRevisionIdPathSchema())
  166. @hapic.input_query(PageQuerySchema())
  167. @hapic.output_file([])
  168. def preview_pdf_revision(self, context, request: TracimRequest, hapic_data=None):
  169. app_config = request.registry.settings['CFG']
  170. api = ContentApi(
  171. current_user=request.current_user,
  172. session=request.dbsession,
  173. config=app_config,
  174. )
  175. content = api.get_one(
  176. hapic_data.path.content_id,
  177. content_type=ContentType.Any
  178. )
  179. revision = api.get_one_revision(
  180. revision_id=hapic_data.path.revision_id,
  181. content=content
  182. )
  183. pdf_preview_path = api.get_pdf_preview_path(
  184. revision.content_id,
  185. revision.revision_id,
  186. page=hapic_data.query.page
  187. )
  188. return FileResponse(pdf_preview_path)
  189. # jpg
  190. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  191. @require_workspace_role(UserRoleInWorkspace.READER)
  192. @require_content_types([file_type])
  193. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  194. @hapic.input_query(PageQuerySchema())
  195. @hapic.output_file([])
  196. def preview_jpg(self, context, request: TracimRequest, hapic_data=None):
  197. app_config = request.registry.settings['CFG']
  198. api = ContentApi(
  199. current_user=request.current_user,
  200. session=request.dbsession,
  201. config=app_config,
  202. )
  203. content = api.get_one(
  204. hapic_data.path.content_id,
  205. content_type=ContentType.Any
  206. )
  207. allowed_dim = api.get_jpg_preview_allowed_dim()
  208. jpg_preview_path = api.get_jpg_preview_path(
  209. content_id=content.content_id,
  210. revision_id=content.revision_id,
  211. page=hapic_data.query.page,
  212. width=allowed_dim.dimensions[0].width,
  213. height=allowed_dim.dimensions[0].height,
  214. )
  215. return FileResponse(jpg_preview_path)
  216. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  217. @require_workspace_role(UserRoleInWorkspace.READER)
  218. @require_content_types([file_type])
  219. @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST)
  220. @hapic.input_query(PageQuerySchema())
  221. @hapic.input_path(ContentPreviewSizedPathSchema())
  222. @hapic.output_file([])
  223. def sized_preview_jpg(self, context, request: TracimRequest, hapic_data=None):
  224. app_config = request.registry.settings['CFG']
  225. api = ContentApi(
  226. current_user=request.current_user,
  227. session=request.dbsession,
  228. config=app_config,
  229. )
  230. content = api.get_one(
  231. hapic_data.path.content_id,
  232. content_type=ContentType.Any
  233. )
  234. jpg_preview_path = api.get_jpg_preview_path(
  235. content_id=content.content_id,
  236. revision_id=content.revision_id,
  237. page=hapic_data.query.page,
  238. height=hapic_data.path.height,
  239. width=hapic_data.path.width,
  240. )
  241. return FileResponse(jpg_preview_path)
  242. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  243. @require_workspace_role(UserRoleInWorkspace.READER)
  244. @require_content_types([file_type])
  245. @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST)
  246. @hapic.input_path(RevisionPreviewSizedPathSchema())
  247. @hapic.input_query(PageQuerySchema())
  248. @hapic.output_file([])
  249. def sized_preview_jpg_revision(self, context, request: TracimRequest, hapic_data=None):
  250. app_config = request.registry.settings['CFG']
  251. api = ContentApi(
  252. current_user=request.current_user,
  253. session=request.dbsession,
  254. config=app_config,
  255. )
  256. content = api.get_one(
  257. hapic_data.path.content_id,
  258. content_type=ContentType.Any
  259. )
  260. revision = api.get_one_revision(
  261. revision_id=hapic_data.path.revision_id,
  262. content=content
  263. )
  264. jpg_preview_path = api.get_jpg_preview_path(
  265. content_id=content.content_id,
  266. revision_id=revision.revision_id,
  267. page=hapic_data.query.page,
  268. height=hapic_data.path.height,
  269. width=hapic_data.path.width,
  270. )
  271. return FileResponse(jpg_preview_path)
  272. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  273. @require_workspace_role(UserRoleInWorkspace.READER)
  274. @require_content_types([file_type])
  275. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  276. @hapic.output_body(AllowedJpgPreviewDimSchema())
  277. def allowed_dim_preview_jpg(self, context, request: TracimRequest, hapic_data=None):
  278. app_config = request.registry.settings['CFG']
  279. api = ContentApi(
  280. current_user=request.current_user,
  281. session=request.dbsession,
  282. config=app_config,
  283. )
  284. return api.get_jpg_preview_allowed_dim()
  285. # File infos
  286. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  287. @require_workspace_role(UserRoleInWorkspace.READER)
  288. @require_content_types([file_type])
  289. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  290. @hapic.output_body(FileContentSchema())
  291. def get_file_infos(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: # nopep8
  292. """
  293. Get thread content
  294. """
  295. app_config = request.registry.settings['CFG']
  296. api = ContentApi(
  297. current_user=request.current_user,
  298. session=request.dbsession,
  299. config=app_config,
  300. )
  301. content = api.get_one(
  302. hapic_data.path.content_id,
  303. content_type=ContentType.Any
  304. )
  305. return api.get_content_in_context(content)
  306. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  307. @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
  308. @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
  309. @require_content_types([file_type])
  310. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  311. @hapic.input_body(FileContentModifySchema())
  312. @hapic.output_body(FileContentSchema())
  313. def update_file_info(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: # nopep8
  314. """
  315. update thread
  316. """
  317. app_config = request.registry.settings['CFG']
  318. api = ContentApi(
  319. current_user=request.current_user,
  320. session=request.dbsession,
  321. config=app_config,
  322. )
  323. content = api.get_one(
  324. hapic_data.path.content_id,
  325. content_type=ContentType.Any
  326. )
  327. with new_revision(
  328. session=request.dbsession,
  329. tm=transaction.manager,
  330. content=content
  331. ):
  332. api.update_content(
  333. item=content,
  334. new_label=hapic_data.body.label,
  335. new_content=hapic_data.body.raw_content,
  336. )
  337. api.save(content)
  338. return api.get_content_in_context(content)
  339. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  340. @require_workspace_role(UserRoleInWorkspace.READER)
  341. @require_content_types([file_type])
  342. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  343. @hapic.output_body(FileRevisionSchema(many=True))
  344. def get_file_revisions(
  345. self,
  346. context,
  347. request: TracimRequest,
  348. hapic_data=None
  349. ) -> typing.List[RevisionInContext]:
  350. """
  351. get file revisions
  352. """
  353. app_config = request.registry.settings['CFG']
  354. api = ContentApi(
  355. current_user=request.current_user,
  356. session=request.dbsession,
  357. config=app_config,
  358. )
  359. content = api.get_one(
  360. hapic_data.path.content_id,
  361. content_type=ContentType.Any
  362. )
  363. revisions = content.revisions
  364. return [
  365. api.get_revision_in_context(revision)
  366. for revision in revisions
  367. ]
  368. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  369. @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
  370. @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
  371. @require_content_types([file_type])
  372. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  373. @hapic.input_body(SetContentStatusSchema())
  374. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  375. def set_file_status(self, context, request: TracimRequest, hapic_data=None) -> None: # nopep8
  376. """
  377. set file status
  378. """
  379. app_config = request.registry.settings['CFG']
  380. api = ContentApi(
  381. current_user=request.current_user,
  382. session=request.dbsession,
  383. config=app_config,
  384. )
  385. content = api.get_one(
  386. hapic_data.path.content_id,
  387. content_type=ContentType.Any
  388. )
  389. with new_revision(
  390. session=request.dbsession,
  391. tm=transaction.manager,
  392. content=content
  393. ):
  394. api.set_status(
  395. content,
  396. hapic_data.body.status,
  397. )
  398. api.save(content)
  399. return
  400. def bind(self, configurator: Configurator) -> None:
  401. # file info #
  402. # Get file info
  403. configurator.add_route(
  404. 'file_info',
  405. '/workspaces/{workspace_id}/files/{content_id}',
  406. request_method='GET'
  407. )
  408. configurator.add_view(self.get_file_infos, route_name='file_info') # nopep8
  409. # update file
  410. configurator.add_route(
  411. 'update_file_info',
  412. '/workspaces/{workspace_id}/files/{content_id}',
  413. request_method='PUT'
  414. ) # nopep8
  415. configurator.add_view(self.update_file_info, route_name='update_file_info') # nopep8
  416. # raw file #
  417. # upload raw file
  418. configurator.add_route(
  419. 'upload_file',
  420. '/workspaces/{workspace_id}/files/{content_id}/raw', # nopep8
  421. request_method='PUT'
  422. )
  423. configurator.add_view(self.upload_file, route_name='upload_file') # nopep8
  424. # download raw file
  425. configurator.add_route(
  426. 'download_file',
  427. '/workspaces/{workspace_id}/files/{content_id}/raw', # nopep8
  428. request_method='GET'
  429. )
  430. configurator.add_view(self.download_file, route_name='download_file') # nopep8
  431. # download raw file of revision
  432. configurator.add_route(
  433. 'download_revision',
  434. '/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/raw', # nopep8
  435. request_method='GET'
  436. )
  437. configurator.add_view(self.download_revisions_file, route_name='download_revision') # nopep8
  438. # previews #
  439. # get preview pdf full
  440. configurator.add_route(
  441. 'preview_pdf_full',
  442. '/workspaces/{workspace_id}/files/{content_id}/preview/pdf/full', # nopep8
  443. request_method='GET'
  444. )
  445. configurator.add_view(self.preview_pdf_full, route_name='preview_pdf_full') # nopep8
  446. # get preview pdf
  447. configurator.add_route(
  448. 'preview_pdf',
  449. '/workspaces/{workspace_id}/files/{content_id}/preview/pdf', # nopep8
  450. request_method='GET'
  451. )
  452. configurator.add_view(self.preview_pdf, route_name='preview_pdf') # nopep8
  453. # get preview jpg allowed dims
  454. configurator.add_route(
  455. 'allowed_dim_preview_jpg',
  456. '/workspaces/{workspace_id}/files/{content_id}/preview/jpg/allowed_dims', # nopep8
  457. request_method='GET'
  458. )
  459. configurator.add_view(self.allowed_dim_preview_jpg, route_name='allowed_dim_preview_jpg') # nopep8
  460. # get preview jpg
  461. configurator.add_route(
  462. 'preview_jpg',
  463. '/workspaces/{workspace_id}/files/{content_id}/preview/jpg', # nopep8
  464. request_method='GET'
  465. )
  466. configurator.add_view(self.preview_jpg, route_name='preview_jpg') # nopep8
  467. # get preview jpg with size
  468. configurator.add_route(
  469. 'sized_preview_jpg',
  470. '/workspaces/{workspace_id}/files/{content_id}/preview/jpg/{width}x{height}', # nopep8
  471. request_method='GET'
  472. )
  473. configurator.add_view(self.sized_preview_jpg, route_name='sized_preview_jpg') # nopep8
  474. # get jpg preview for revision
  475. configurator.add_route(
  476. 'sized_preview_jpg_revision',
  477. '/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/jpg/{width}x{height}', # nopep8
  478. request_method='GET'
  479. )
  480. configurator.add_view(self.sized_preview_jpg_revision, route_name='sized_preview_jpg_revision') # nopep8
  481. # get jpg preview for revision
  482. configurator.add_route(
  483. 'preview_pdf_revision',
  484. '/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/pdf', # nopep8
  485. request_method='GET'
  486. )
  487. configurator.add_view(self.preview_pdf_revision, route_name='preview_pdf_revision') # nopep8
  488. # others #
  489. # get file revisions
  490. configurator.add_route(
  491. 'file_revisions',
  492. '/workspaces/{workspace_id}/files/{content_id}/revisions', # nopep8
  493. request_method='GET'
  494. )
  495. configurator.add_view(self.get_file_revisions, route_name='file_revisions') # nopep8
  496. # get file status
  497. configurator.add_route(
  498. 'set_file_status',
  499. '/workspaces/{workspace_id}/files/{content_id}/status', # nopep8
  500. request_method='PUT'
  501. )
  502. configurator.add_view(self.set_file_status, route_name='set_file_status') # nopep8