file_controller.py 20KB

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