file_controller.py 21KB

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