file_controller.py 22KB

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