OpenREM 1.0.0b2__py3-none-any.whl → 1.0.0b3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. openrem/locale/de/LC_MESSAGES/django.po +1060 -1059
  2. openrem/locale/django.pot +973 -972
  3. openrem/locale/es_MX/LC_MESSAGES/django.po +1049 -1048
  4. openrem/locale/it/LC_MESSAGES/django.po +1044 -1043
  5. openrem/locale/lt/LC_MESSAGES/django.po +989 -988
  6. openrem/locale/nb_NO/LC_MESSAGES/django.po +985 -984
  7. openrem/locale/pt_BR/LC_MESSAGES/django.po +1003 -1002
  8. openrem/manage.py +10 -10
  9. openrem/openremproject/__init__.py +1 -1
  10. openrem/openremproject/local_settings.py.linux +128 -128
  11. openrem/openremproject/local_settings.py.windows +144 -144
  12. openrem/openremproject/local_settings.py.windows-sqlite3 +129 -129
  13. openrem/openremproject/settings.py +278 -278
  14. openrem/openremproject/urls.py +32 -32
  15. openrem/openremproject/wsgi.py.example +28 -28
  16. openrem/remapp/__init__.py +2 -2
  17. openrem/remapp/admin.py +31 -31
  18. openrem/remapp/exports/ct_export.py +780 -753
  19. openrem/remapp/exports/dx_export.py +817 -805
  20. openrem/remapp/exports/export_common.py +931 -951
  21. openrem/remapp/exports/export_common_pandas.py +2422 -0
  22. openrem/remapp/exports/exportviews.py +815 -860
  23. openrem/remapp/exports/mg_csv_nhsbsp.py +292 -292
  24. openrem/remapp/exports/mg_export.py +673 -510
  25. openrem/remapp/exports/nm_export.py +796 -575
  26. openrem/remapp/exports/rf_export.py +1418 -1431
  27. openrem/remapp/extractors/ct_philips.py +424 -414
  28. openrem/remapp/extractors/ct_toshiba.py +2116 -2108
  29. openrem/remapp/extractors/dx.py +1033 -952
  30. openrem/remapp/extractors/extract_common.py +817 -817
  31. openrem/remapp/extractors/import_views.py +426 -426
  32. openrem/remapp/extractors/mam.py +685 -672
  33. openrem/remapp/extractors/nm_image.py +439 -431
  34. openrem/remapp/extractors/ptsizecsv2db.py +368 -368
  35. openrem/remapp/extractors/rdsr.py +667 -654
  36. openrem/remapp/extractors/rdsr_methods.py +1771 -1768
  37. openrem/remapp/extractors/rrdsr_methods.py +630 -622
  38. openrem/remapp/fixtures/openskin_safelist.json +11 -11
  39. openrem/remapp/forms.py +2286 -2277
  40. openrem/remapp/interface/chart_functions.py +2412 -2393
  41. openrem/remapp/interface/mod_filters.py +1241 -1243
  42. openrem/remapp/migrations/0001_initial.py.1-0-upgrade +1043 -1043
  43. openrem/remapp/models.py +3418 -3407
  44. openrem/remapp/netdicom/dicomviews.py +681 -683
  45. openrem/remapp/netdicom/qrscu.py +2646 -2646
  46. openrem/remapp/netdicom/tools.py +134 -134
  47. openrem/remapp/static/css/bootstrap-theme.css +587 -587
  48. openrem/remapp/static/css/bootstrap-theme.min.css +4 -4
  49. openrem/remapp/static/css/bootstrap.css +6800 -6800
  50. openrem/remapp/static/css/bootstrap.min.css +4 -4
  51. openrem/remapp/static/css/datepicker3.css +790 -790
  52. openrem/remapp/static/css/jquery.qtip.min.css +2 -2
  53. openrem/remapp/static/css/openrem-extra.css +442 -442
  54. openrem/remapp/static/css/openrem.css +96 -96
  55. openrem/remapp/static/css/registration.css +34 -34
  56. openrem/remapp/static/fonts/glyphicons-halflings-regular.svg +287 -287
  57. openrem/remapp/static/js/bootstrap-datepicker.js +1671 -1671
  58. openrem/remapp/static/js/bootstrap.js +2363 -2363
  59. openrem/remapp/static/js/bootstrap.min.js +6 -6
  60. openrem/remapp/static/js/charts/chartCommonFunctions.js +75 -75
  61. openrem/remapp/static/js/charts/chartFullScreen.js +41 -41
  62. openrem/remapp/static/js/charts/ctChartAjax.js +331 -331
  63. openrem/remapp/static/js/charts/dxChartAjax.js +290 -290
  64. openrem/remapp/static/js/charts/mgChartAjax.js +144 -144
  65. openrem/remapp/static/js/charts/nmChartAjax.js +64 -64
  66. openrem/remapp/static/js/charts/plotly-2.35.2.min.js +8 -0
  67. openrem/remapp/static/js/charts/rfChartAjax.js +128 -128
  68. openrem/remapp/static/js/chroma.min.js +32 -32
  69. openrem/remapp/static/js/datepicker.js +5 -5
  70. openrem/remapp/static/js/dicom.js +115 -115
  71. openrem/remapp/static/js/django_reverse/reverse.js +13 -13
  72. openrem/remapp/static/js/formatDate.js +7 -7
  73. openrem/remapp/static/js/html5shiv.min.js +8 -8
  74. openrem/remapp/static/js/jquery-1.11.0.min.js +4 -4
  75. openrem/remapp/static/js/npm.js +12 -12
  76. openrem/remapp/static/js/respond.min.js +4 -4
  77. openrem/remapp/static/js/skin-dose-maps/jquery.qtip.min.js +4 -4
  78. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMap3dHUDObject.js +112 -112
  79. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMap3dObject.js +367 -367
  80. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMap3dPersonObject.js +158 -158
  81. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapColourScaleObject.js +153 -153
  82. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapObject.js +367 -367
  83. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapping.js +584 -584
  84. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMapping3d.js +255 -255
  85. openrem/remapp/static/js/skin-dose-maps/rfSkinDoseMappingAjax.js +267 -212
  86. openrem/remapp/static/js/skin-dose-maps/three.min.js +835 -835
  87. openrem/remapp/static/js/sorttable.js +495 -495
  88. openrem/remapp/templates/base.html +253 -253
  89. openrem/remapp/templates/registration/changepassword.html +25 -25
  90. openrem/remapp/templates/registration/changepassworddone.html +12 -12
  91. openrem/remapp/templates/registration/login.html +42 -42
  92. openrem/remapp/templates/remapp/backgroundtaskmaximumrows_form.html +29 -29
  93. openrem/remapp/templates/remapp/base.html +1 -1
  94. openrem/remapp/templates/remapp/ctdetail.html +235 -235
  95. openrem/remapp/templates/remapp/ctfiltered.html +310 -310
  96. openrem/remapp/templates/remapp/dicomdeletesettings_form.html +31 -31
  97. openrem/remapp/templates/remapp/dicomqr.html +147 -147
  98. openrem/remapp/templates/remapp/dicomquerydetails.html +83 -83
  99. openrem/remapp/templates/remapp/dicomqueryimages.html +49 -49
  100. openrem/remapp/templates/remapp/dicomqueryseries.html +109 -109
  101. openrem/remapp/templates/remapp/dicomquerysummary.html +48 -48
  102. openrem/remapp/templates/remapp/dicomremoteqr_confirm_delete.html +60 -60
  103. openrem/remapp/templates/remapp/dicomremoteqr_form.html +32 -32
  104. openrem/remapp/templates/remapp/dicomstorescp_confirm_delete.html +53 -53
  105. openrem/remapp/templates/remapp/dicomstorescp_form.html +48 -48
  106. openrem/remapp/templates/remapp/dicomsummary.html +257 -257
  107. openrem/remapp/templates/remapp/displaychartoptions.html +184 -184
  108. openrem/remapp/templates/remapp/displayhomepageoptions.html +57 -57
  109. openrem/remapp/templates/remapp/displayname-count.html +6 -6
  110. openrem/remapp/templates/remapp/displayname-last-date.html +3 -3
  111. openrem/remapp/templates/remapp/displayname-modality.html +86 -105
  112. openrem/remapp/templates/remapp/displayname-skinmap.html +18 -18
  113. openrem/remapp/templates/remapp/displaynameupdate.html +100 -100
  114. openrem/remapp/templates/remapp/displaynameview.html +222 -219
  115. openrem/remapp/templates/remapp/dxdetail.html +176 -176
  116. openrem/remapp/templates/remapp/dxfiltered.html +324 -324
  117. openrem/remapp/templates/remapp/exports-active.html +25 -25
  118. openrem/remapp/templates/remapp/exports-complete.html +35 -35
  119. openrem/remapp/templates/remapp/exports-error.html +26 -26
  120. openrem/remapp/templates/remapp/exports-queue.html +18 -18
  121. openrem/remapp/templates/remapp/exports.html +191 -191
  122. openrem/remapp/templates/remapp/failed_summary_list.html +27 -27
  123. openrem/remapp/templates/remapp/filteredbase.html +162 -162
  124. openrem/remapp/templates/remapp/highdosemetricalertsettings_form.html +76 -76
  125. openrem/remapp/templates/remapp/home-list-modalities.html +94 -94
  126. openrem/remapp/templates/remapp/home.html +202 -202
  127. openrem/remapp/templates/remapp/list_filters.html +24 -24
  128. openrem/remapp/templates/remapp/mgdetail.html +160 -138
  129. openrem/remapp/templates/remapp/mgfiltered.html +311 -311
  130. openrem/remapp/templates/remapp/nmdetail.html +300 -300
  131. openrem/remapp/templates/remapp/nmfiltered.html +255 -255
  132. openrem/remapp/templates/remapp/notpatient.html +190 -190
  133. openrem/remapp/templates/remapp/notpatientindicators_form_base.html +81 -81
  134. openrem/remapp/templates/remapp/notpatientindicatorsid_confirm_delete.html +54 -54
  135. openrem/remapp/templates/remapp/notpatientindicatorsid_form.html +23 -23
  136. openrem/remapp/templates/remapp/notpatientindicatorsname_confirm_delete.html +54 -54
  137. openrem/remapp/templates/remapp/notpatientindicatorsname_form.html +23 -23
  138. openrem/remapp/templates/remapp/notpatientindicatorsname_form_base.html +85 -85
  139. openrem/remapp/templates/remapp/openskinsafelist_add.html +130 -130
  140. openrem/remapp/templates/remapp/openskinsafelist_confirm_delete.html +100 -100
  141. openrem/remapp/templates/remapp/openskinsafelist_form.html +207 -207
  142. openrem/remapp/templates/remapp/patientidsettings_form.html +83 -83
  143. openrem/remapp/templates/remapp/populate_summary_progress.html +83 -83
  144. openrem/remapp/templates/remapp/populate_summary_progress_error.html +36 -36
  145. openrem/remapp/templates/remapp/review_failed_imports.html +157 -157
  146. openrem/remapp/templates/remapp/review_failed_study.html +41 -41
  147. openrem/remapp/templates/remapp/review_studies_delete_button.html +20 -20
  148. openrem/remapp/templates/remapp/review_study.html +19 -19
  149. openrem/remapp/templates/remapp/review_summary_list.html +245 -245
  150. openrem/remapp/templates/remapp/rf_dose_alert_email_template.html +14 -1
  151. openrem/remapp/templates/remapp/rfalertnotificationsview.html +59 -59
  152. openrem/remapp/templates/remapp/rfdetail.html +547 -543
  153. openrem/remapp/templates/remapp/rfdetailbase.html +18 -18
  154. openrem/remapp/templates/remapp/rffiltered.html +404 -404
  155. openrem/remapp/templates/remapp/sizeimports.html +119 -119
  156. openrem/remapp/templates/remapp/sizeprocess.html +96 -96
  157. openrem/remapp/templates/remapp/sizeupload.html +110 -110
  158. openrem/remapp/templates/remapp/skindosemapcalcsettings_form.html +28 -28
  159. openrem/remapp/templates/remapp/standardname-modality.html +69 -69
  160. openrem/remapp/templates/remapp/standardnames_confirm_delete.html +71 -71
  161. openrem/remapp/templates/remapp/standardnames_form.html +87 -87
  162. openrem/remapp/templates/remapp/standardnamesettings_form.html +41 -41
  163. openrem/remapp/templates/remapp/standardnamesrefreshall.html +92 -92
  164. openrem/remapp/templates/remapp/standardnameview.html +103 -103
  165. openrem/remapp/templates/remapp/study_confirm_delete.html +147 -147
  166. openrem/remapp/templates/remapp/task_admin.html +265 -265
  167. openrem/remapp/templates/remapp/tasks.html +76 -76
  168. openrem/remapp/templatetags/formfilters.py +13 -13
  169. openrem/remapp/templatetags/proper_paginate.py +38 -38
  170. openrem/remapp/templatetags/remappduration.py +36 -36
  171. openrem/remapp/templatetags/sigdig.py +38 -38
  172. openrem/remapp/templatetags/sort_class_property_value.py +15 -15
  173. openrem/remapp/templatetags/update_variable.py +20 -20
  174. openrem/remapp/templatetags/url_replace.py +25 -25
  175. openrem/remapp/tests/test_charts_common.py +202 -202
  176. openrem/remapp/tests/test_charts_ct.py +7111 -7111
  177. openrem/remapp/tests/test_charts_dx.py +3513 -3513
  178. openrem/remapp/tests/test_charts_mg.py +1116 -1115
  179. openrem/remapp/tests/test_dcmdatetime.py +189 -189
  180. openrem/remapp/tests/test_dicom_qr.py +2580 -2580
  181. openrem/remapp/tests/test_display_name.py +274 -274
  182. openrem/remapp/tests/test_export_ct_xlsx.py +272 -248
  183. openrem/remapp/tests/test_export_dx_xlsx.py +137 -134
  184. openrem/remapp/tests/test_export_mammo_csv.py +242 -242
  185. openrem/remapp/tests/test_export_rf_xlsx.py +246 -246
  186. openrem/remapp/tests/test_files/DX-Im-DRGEM.dcm +0 -0
  187. openrem/remapp/tests/test_files/MG-RDSR-GEPristina-2D.dcm +0 -0
  188. openrem/remapp/tests/test_files/MG-RDSR-GEPristina-DBT.dcm +0 -0
  189. openrem/remapp/tests/test_files/MG-RDSR-Giotto-DBT.dcm +0 -0
  190. openrem/remapp/tests/test_files/skin_map_alphenix.py +590 -590
  191. openrem/remapp/tests/test_files/skin_map_zee.py +354 -354
  192. openrem/remapp/tests/test_filters_ct.py +321 -321
  193. openrem/remapp/tests/test_filters_dx.py +92 -92
  194. openrem/remapp/tests/test_filters_mammo.py +183 -183
  195. openrem/remapp/tests/test_filters_rf.py +118 -118
  196. openrem/remapp/tests/test_get_values.py +72 -72
  197. openrem/remapp/tests/test_hash_id.py +65 -65
  198. openrem/remapp/tests/test_import_ct_esr_ge.py +3034 -3034
  199. openrem/remapp/tests/test_import_ct_philips_rdsr.py +42 -42
  200. openrem/remapp/tests/test_import_ct_rdsr_multiple.py +256 -256
  201. openrem/remapp/tests/test_import_ct_rdsr_siemens.py +827 -827
  202. openrem/remapp/tests/test_import_ct_rdsr_spectrumdynamics.py +91 -91
  203. openrem/remapp/tests/test_import_ct_rdsr_toshiba_dosecheck.py +67 -67
  204. openrem/remapp/tests/test_import_ct_rdsr_toshiba_multivaluesd.py +33 -33
  205. openrem/remapp/tests/test_import_ct_rdsr_toshiba_pixelmed.py +118 -118
  206. openrem/remapp/tests/test_import_ct_sc_philips.py +44 -44
  207. openrem/remapp/tests/test_import_dual_rdsr.py +110 -110
  208. openrem/remapp/tests/test_import_dx.py +1267 -1191
  209. openrem/remapp/tests/test_import_dx_rdsr.py +1250 -1253
  210. openrem/remapp/tests/test_import_mam.py +438 -438
  211. openrem/remapp/tests/test_import_mg_im_hol_proj.py +46 -46
  212. openrem/remapp/tests/test_import_mg_rdsr.py +586 -586
  213. openrem/remapp/tests/test_import_nm_image.py +420 -420
  214. openrem/remapp/tests/test_import_nm_siemens_rdsr.py +396 -396
  215. openrem/remapp/tests/test_import_px.py +161 -161
  216. openrem/remapp/tests/test_import_rf_rdsr.py +420 -418
  217. openrem/remapp/tests/test_missing_date.py +42 -42
  218. openrem/remapp/tests/test_not_patient.py +60 -60
  219. openrem/remapp/tests/test_openskin.py +272 -272
  220. openrem/remapp/tests/test_patient_id_settings.py +72 -72
  221. openrem/remapp/tests/test_pt_size_import.py +232 -232
  222. openrem/remapp/tests/test_rf_detail.py +113 -113
  223. openrem/remapp/tests/test_rf_high_dose_alert.py +361 -361
  224. openrem/remapp/tools/background.py +361 -361
  225. openrem/remapp/tools/check_standard_name_status.py +47 -0
  226. openrem/remapp/tools/check_uid.py +70 -70
  227. openrem/remapp/tools/dcmdatetime.py +248 -248
  228. openrem/remapp/tools/default_import.py +44 -47
  229. openrem/remapp/tools/get_values.py +230 -230
  230. openrem/remapp/tools/hash_id.py +58 -58
  231. openrem/remapp/tools/make_skin_map.py +448 -406
  232. openrem/remapp/tools/not_patient_indicators.py +72 -72
  233. openrem/remapp/tools/openskin/calc_exp_map.py +173 -173
  234. openrem/remapp/tools/openskin/geomclass.py +475 -475
  235. openrem/remapp/tools/openskin/geomfunc.py +433 -432
  236. openrem/remapp/tools/openskin/skinmap.py +417 -417
  237. openrem/remapp/tools/populate_summary.py +185 -193
  238. openrem/remapp/tools/save_skin_map_structure.py +73 -73
  239. openrem/remapp/tools/send_high_dose_alert_emails.py +238 -207
  240. openrem/remapp/urls.py +456 -448
  241. openrem/remapp/version.py +11 -11
  242. openrem/remapp/views.py +1147 -1052
  243. openrem/remapp/views_admin.py +3876 -3936
  244. openrem/remapp/views_charts_ct.py +2110 -2058
  245. openrem/remapp/views_charts_dx.py +1906 -1836
  246. openrem/remapp/views_charts_mg.py +1349 -1196
  247. openrem/remapp/views_charts_nm.py +535 -535
  248. openrem/remapp/views_charts_rf.py +1219 -1241
  249. openrem/remapp/views_openskin.py +379 -384
  250. openrem/sample-config/openrem-consumer.service +12 -12
  251. openrem/sample-config/openrem-gunicorn.service +13 -13
  252. openrem/sample-config/openrem-server +14 -13
  253. openrem/sample-config/openrem_orthanc_config_linux.lua +454 -454
  254. openrem/sample-config/openrem_orthanc_config_windows.lua +455 -455
  255. openrem/sample-config/queue-init.bat +73 -73
  256. openrem/scripts/openrem_ctphilips.py +25 -25
  257. openrem/scripts/openrem_cttoshiba.py +28 -28
  258. openrem/scripts/openrem_dx.py +22 -22
  259. openrem/scripts/openrem_mg.py +22 -22
  260. openrem/scripts/openrem_nm.py +22 -22
  261. openrem/scripts/openrem_ptsizecsv.py +17 -17
  262. openrem/scripts/openrem_qr.py +12 -12
  263. openrem/scripts/openrem_rdsr.py +25 -25
  264. {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info}/METADATA +39 -29
  265. openrem-1.0.0b3.dist-info/RECORD +379 -0
  266. {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info}/WHEEL +1 -1
  267. {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info/licenses}/COPYING-GPLv3 +674 -674
  268. {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info/licenses}/LICENSE +22 -22
  269. OpenREM-1.0.0b2.dist-info/RECORD +0 -373
  270. openrem/remapp/static/js/charts/plotly-2.17.1.min.js +0 -8
  271. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_ctphilips.py +0 -0
  272. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_cttoshiba.py +0 -0
  273. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_dx.py +0 -0
  274. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_mg.py +0 -0
  275. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_nm.py +0 -0
  276. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_ptsizecsv.py +0 -0
  277. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_qr.py +0 -0
  278. {OpenREM-1.0.0b2.data → openrem-1.0.0b3.data}/scripts/openrem_rdsr.py +0 -0
  279. {OpenREM-1.0.0b2.dist-info → openrem-1.0.0b3.dist-info}/top_level.txt +0 -0
@@ -1,860 +1,815 @@
1
- # OpenREM - Radiation Exposure Monitoring tools for the physicist
2
- # Copyright (C) 2012,2013 The Royal Marsden NHS Foundation Trust
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # Additional permission under section 7 of GPLv3:
15
- # You shall not make any use of the name of The Royal Marsden NHS
16
- # Foundation trust in connection with this Program in any press or
17
- # other public announcement without the prior written consent of
18
- # The Royal Marsden NHS Foundation Trust.
19
- #
20
- # You should have received a copy of the GNU General Public License
21
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
22
-
23
- """
24
- .. module:: exportviews.py
25
- :synopsis: Module to render appropriate content according to request, specific to the exports.
26
-
27
- .. moduleauthor:: Ed McDonagh
28
-
29
- """
30
-
31
- # Following two lines added so that sphinx autodocumentation works.
32
- import os
33
-
34
- os.environ["DJANGO_SETTINGS_MODULE"] = "openremproject.settings"
35
-
36
- import logging
37
- from django.views.decorators.csrf import csrf_exempt
38
- from django.contrib import messages
39
- from django.contrib.auth.decorators import login_required
40
- from django.http import HttpResponse
41
- from django.shortcuts import render
42
- from django.urls import reverse_lazy
43
- import remapp
44
- from openrem.remapp.tools.background import ( # pylint: disable=wrong-import-position
45
- run_in_background,
46
- terminate_background,
47
- get_queued_tasks,
48
- remove_task_from_queue,
49
- )
50
-
51
- logger = logging.getLogger(__name__)
52
-
53
-
54
- def include_pid(request, name, pat_id):
55
- """
56
- Check if user is allowed to export PID, then check if they have asked to.
57
- :param request: request so we can determine the user and therefore groups
58
- :param name: string, 0 or 1 from URL indicating if names should be exported
59
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
60
- :return: dict, with pidgroup, include_names and include_pat_id as bools
61
- """
62
-
63
- pid = bool(request.user.groups.filter(name="pidgroup"))
64
-
65
- include_names = False
66
- include_pat_id = False
67
- if pid:
68
- try:
69
- if int(name): # Will be unicode from URL
70
- include_names = True
71
- except ValueError: # If anything else comes in, just don't export that column
72
- pass
73
- try:
74
- if int(pat_id):
75
- include_pat_id = True
76
- except ValueError:
77
- pass
78
-
79
- return {
80
- "pidgroup": pid,
81
- "include_names": include_names,
82
- "include_pat_id": include_pat_id,
83
- }
84
-
85
-
86
- @csrf_exempt
87
- @login_required
88
- def ctcsv1(request, name=None, pat_id=None):
89
- """
90
- View to launch task to export CT studies to csv file
91
-
92
- :param request: Contains the database filtering parameters. Also used to get user group.
93
- :param name: string, 0 or 1 from URL indicating if names should be exported
94
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
95
- :type request: GET
96
- """
97
- from django.shortcuts import redirect
98
- from remapp.exports.ct_export import ct_csv
99
-
100
- pid = include_pid(request, name, pat_id)
101
-
102
- if request.user.groups.filter(name="exportgroup"):
103
- # The 'ct_acquisition_type' filter values must be passed to ct_csv as a list, so convert the GET to a dict and
104
- # then update the 'ct_acquisition_type' value with a list.
105
- filter_dict = request.GET.dict()
106
- if "ct_acquisition_type" in filter_dict:
107
- filter_dict["ct_acquisition_type"] = request.GET.getlist(
108
- "ct_acquisition_type"
109
- )
110
-
111
- job = run_in_background(
112
- ct_csv,
113
- "export_ct",
114
- filter_dict,
115
- pid["pidgroup"],
116
- pid["include_names"],
117
- pid["include_pat_id"],
118
- request.user.id,
119
- )
120
- logger.debug(f"Export CT to CSV job is {job.id}")
121
- return redirect(reverse_lazy("export"))
122
-
123
-
124
- @csrf_exempt
125
- @login_required
126
- def ctxlsx1(request, name=None, pat_id=None):
127
- """
128
- View to launch task to export CT studies to xlsx file
129
-
130
- :param request: Contains the database filtering parameters. Also used to get user group.
131
- :param name: string, 0 or 1 from URL indicating if names should be exported
132
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
133
- :type request: GET
134
- """
135
- from django.shortcuts import redirect
136
- from remapp.exports.ct_export import ctxlsx
137
-
138
- pid = include_pid(request, name, pat_id)
139
-
140
- if request.user.groups.filter(name="exportgroup"):
141
- # The 'ct_acquisition_type' filter values must be passed to ctxlsx as a list, so convert the GET to a dict and
142
- # then update the 'ct_acquisition_type' value with a list.
143
- filter_dict = request.GET.dict()
144
- if "ct_acquisition_type" in filter_dict:
145
- filter_dict["ct_acquisition_type"] = request.GET.getlist(
146
- "ct_acquisition_type"
147
- )
148
-
149
- job = run_in_background(
150
- ctxlsx,
151
- "export_ct",
152
- filter_dict,
153
- pid["pidgroup"],
154
- pid["include_names"],
155
- pid["include_pat_id"],
156
- request.user.id,
157
- )
158
- logger.debug("Export CT to XLSX job is {0}".format(job.id))
159
-
160
- return redirect(reverse_lazy("export"))
161
-
162
-
163
- @csrf_exempt
164
- @login_required
165
- def ct_xlsx_phe2019(request):
166
- """
167
- View to launch task to export CT studies to xlsx file in PHE 2019 CT survey format
168
-
169
- :param request: Contains the database filtering parameters and user details.
170
- """
171
- from django.shortcuts import redirect
172
- from remapp.exports.ct_export import ct_phe_2019
173
-
174
- if request.user.groups.filter(name="exportgroup"):
175
- job = run_in_background(ct_phe_2019, "export_ct", request.GET, request.user.id)
176
- logger.debug("Export CT to XLSX job is {0}".format(job.id))
177
- return redirect(reverse_lazy("export"))
178
-
179
-
180
- @csrf_exempt
181
- @login_required
182
- def nmcsv1(request, name=None, pat_id=None):
183
- """
184
- View to launch task to export NM studies to csv file
185
-
186
- :param request: Contains the database filtering parameters. Also used to get user group.
187
- :param name: string, 0 or 1 from URL indicating if names should be exported
188
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
189
- :type request: GET
190
- """
191
- from django.shortcuts import redirect
192
- from remapp.exports.nm_export import exportNM2csv
193
-
194
- pid = include_pid(request, name, pat_id)
195
-
196
- if request.user.groups.filter(name="exportgroup"):
197
- job = run_in_background(
198
- exportNM2csv,
199
- "export_nm",
200
- request.GET,
201
- pid["pidgroup"],
202
- pid["include_names"],
203
- pid["include_pat_id"],
204
- request.user.id,
205
- )
206
- logger.debug(f"Export NM to CSV job is {job.id}")
207
-
208
- return redirect(reverse_lazy("export"))
209
-
210
-
211
- @csrf_exempt
212
- @login_required
213
- def nmxlsx1(request, name=None, pat_id=None):
214
- """
215
- View to launch celery task to export NM studies to excel file
216
-
217
- :param request: Contains the database filtering parameters. Also used to get user group.
218
- :param name: string, 0 or 1 from URL indicating if names should be exported
219
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
220
- :type request: GET
221
- """
222
- from django.shortcuts import redirect
223
- from remapp.exports.nm_export import exportNM2excel
224
-
225
- pid = include_pid(request, name, pat_id)
226
-
227
- if request.user.groups.filter(name="exportgroup"):
228
- job = run_in_background(
229
- exportNM2excel,
230
- "export_nm",
231
- request.GET,
232
- pid["pidgroup"],
233
- pid["include_names"],
234
- pid["include_pat_id"],
235
- request.user.id,
236
- )
237
- logger.debug(f"Exprt NM to Excel job is {job.id}")
238
-
239
- return redirect(reverse_lazy("export"))
240
-
241
-
242
- @csrf_exempt
243
- @login_required
244
- def dxcsv1(request, name=None, pat_id=None):
245
- """
246
- View to launch task to export DX and CR studies to csv file
247
-
248
- :param request: Contains the database filtering parameters. Also used to get user group.
249
- :param name: string, 0 or 1 from URL indicating if names should be exported
250
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
251
- :type request: GET
252
- """
253
- from django.shortcuts import redirect
254
- from remapp.exports.dx_export import exportDX2excel
255
-
256
- pid = include_pid(request, name, pat_id)
257
-
258
- if request.user.groups.filter(name="exportgroup"):
259
- job = run_in_background(
260
- exportDX2excel,
261
- "export_dx",
262
- request.GET,
263
- pid["pidgroup"],
264
- pid["include_names"],
265
- pid["include_pat_id"],
266
- request.user.id,
267
- )
268
- logger.debug("Export DX to CSV job is {0}".format(job.id))
269
-
270
- return redirect(reverse_lazy("export"))
271
-
272
-
273
- @csrf_exempt
274
- @login_required
275
- def dxxlsx1(request, name=None, pat_id=None):
276
- """
277
- View to launch task to export DX and CR studies to xlsx file
278
-
279
- :param request: Contains the database filtering parameters. Also used to get user group.
280
- :param name: string, 0 or 1 from URL indicating if names should be exported
281
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
282
- :type request: GET
283
- """
284
- from django.shortcuts import redirect
285
- from remapp.exports.dx_export import dxxlsx
286
-
287
- pid = include_pid(request, name, pat_id)
288
-
289
- if request.user.groups.filter(name="exportgroup"):
290
- job = run_in_background(
291
- dxxlsx,
292
- "export_dx",
293
- request.GET,
294
- pid["pidgroup"],
295
- pid["include_names"],
296
- pid["include_pat_id"],
297
- request.user.id,
298
- )
299
- logger.debug("Export DX to XLSX job is {0}".format(job.id))
300
-
301
- return redirect(reverse_lazy("export"))
302
-
303
-
304
- @csrf_exempt
305
- @login_required
306
- def dx_xlsx_phe2019(request, export_type=None):
307
- """
308
- View to launch task to export DX studies to xlsx file in PHE 2019 DX survey format
309
-
310
- :param request: Contains the database filtering parameters and user details.
311
- :param export_type: string, 'projection' or 'exam'
312
- """
313
- import urllib
314
- from django.db.models import Max
315
- from django.shortcuts import redirect
316
- from remapp.exports.dx_export import dx_phe_2019
317
- from remapp.interface.mod_filters import dx_acq_filter
318
-
319
- if request.user.groups.filter(name="exportgroup"):
320
- if export_type in ("exam", "projection"):
321
- bespoke = False
322
- exams = dx_acq_filter(request.GET, pid=False).qs
323
- if not exams.count():
324
- messages.error(request, "No studies in export, nothing to do!")
325
- return redirect(
326
- "{0}?{1}".format(
327
- reverse_lazy("dx_summary_list_filter"),
328
- urllib.urlencode(request.GET),
329
- )
330
- )
331
- max_events_dict = exams.aggregate(Max("number_of_events"))
332
- max_events = max_events_dict["number_of_events__max"]
333
- if "projection" in export_type:
334
- if max_events > 1:
335
- messages.warning(
336
- request,
337
- "PHE 2019 DX Projection export is expecting one exposure per study - "
338
- "some studies selected have more than one. Only the first exposure will "
339
- "be considered.",
340
- )
341
- else:
342
- messages.info(
343
- request, "PHE 2019 DX single projection export started."
344
- )
345
- job = run_in_background(
346
- dx_phe_2019,
347
- "export_dx",
348
- request.GET,
349
- request.user.id,
350
- projection=True,
351
- )
352
- logger.debug(
353
- "Export PHE 2019 DX survey format job is {0}".format(job.id)
354
- )
355
- return redirect(reverse_lazy("export"))
356
- elif "exam" in export_type:
357
- if max_events > 6:
358
- bespoke = True
359
- if max_events > 20:
360
- messages.warning(
361
- request,
362
- "PHE 2019 DX Study sheets expect a maximum of six projections. You "
363
- "need to request a bespoke workbook from PHE. This export has a "
364
- "maximum of {0} projections, but only the first 20 will be included "
365
- "in the main columns of the bespoke worksheet.".format(
366
- max_events
367
- ),
368
- )
369
- else:
370
- messages.warning(
371
- request,
372
- "PHE 2019 DX Study sheets expect a maximum of six projections. This "
373
- "export has a maximum of {0} projections so you will need to request"
374
- " a bespoke workbook from PHE. This has space for 20 "
375
- "projections.".format(max_events),
376
- )
377
- else:
378
- messages.info(request, "PHE 2019 DX Study export started.")
379
- job = run_in_background(
380
- dx_phe_2019,
381
- "export_dx",
382
- request.GET,
383
- request.user.id,
384
- projection=False,
385
- bespoke=bespoke,
386
- )
387
- logger.debug(
388
- "Export PHE 2019 DX survey format job is {0}".format(job.id)
389
- )
390
- return redirect(reverse_lazy("export"))
391
- else:
392
- messages.error(request, "Malformed export URL {0}".format(type))
393
- return redirect(
394
- "{0}?{1}".format(
395
- reverse_lazy("dx_summary_list_filter"),
396
- urllib.urlencode(request.GET),
397
- )
398
- )
399
- else:
400
- messages.error(request, "Only users in the Export group can launch exports")
401
- return redirect(
402
- "{0}?{1}".format(
403
- reverse_lazy("dx_summary_list_filter"), urllib.urlencode(request.GET)
404
- )
405
- )
406
-
407
-
408
- @csrf_exempt
409
- @login_required
410
- def flcsv1(request, name=None, pat_id=None):
411
- """
412
- View to launch task to export fluoroscopy studies to csv file
413
-
414
- :param request: Contains the database filtering parameters. Also used to get user group.
415
- :param name: string, 0 or 1 from URL indicating if names should be exported
416
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
417
- :type request: GET
418
- """
419
- from django.shortcuts import redirect
420
- from remapp.exports.rf_export import exportFL2excel
421
-
422
- pid = include_pid(request, name, pat_id)
423
-
424
- if request.user.groups.filter(name="exportgroup"):
425
- job = run_in_background(
426
- exportFL2excel,
427
- "export_fl",
428
- request.GET,
429
- pid["pidgroup"],
430
- pid["include_names"],
431
- pid["include_pat_id"],
432
- request.user.id,
433
- )
434
- logger.debug("Export Fluoro to CSV job is {0}".format(job.id))
435
-
436
- return redirect(reverse_lazy("export"))
437
-
438
-
439
- @csrf_exempt
440
- @login_required
441
- def rfxlsx1(request, name=None, pat_id=None):
442
- """
443
- View to launch task to export fluoroscopy studies to xlsx file
444
-
445
- :param request: Contains the database filtering parameters. Also used to get user group.
446
- :param name: string, 0 or 1 from URL indicating if names should be exported
447
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
448
- :type request: GET
449
- """
450
- from django.shortcuts import redirect
451
- from remapp.exports.rf_export import rfxlsx
452
-
453
- pid = include_pid(request, name, pat_id)
454
-
455
- if request.user.groups.filter(name="exportgroup"):
456
- job = run_in_background(
457
- rfxlsx,
458
- "export_rf",
459
- request.GET,
460
- pid["pidgroup"],
461
- pid["include_names"],
462
- pid["include_pat_id"],
463
- request.user.id,
464
- )
465
- logger.debug("Export Fluoro to XLSX job is {0}".format(job.id))
466
-
467
- return redirect(reverse_lazy("export"))
468
-
469
-
470
- @csrf_exempt
471
- @login_required
472
- def rfopenskin(request, pk):
473
- """
474
- Create csv export suitable for import to standalone openSkin
475
- :param request: request object
476
- :param pk: primary key of study in GeneralStudyModuleAttr table
477
- """
478
- from django.shortcuts import redirect, get_object_or_404
479
- from remapp.exports.rf_export import rfopenskin
480
- from remapp.models import GeneralStudyModuleAttr
481
-
482
- export = get_object_or_404(GeneralStudyModuleAttr, pk=pk)
483
-
484
- if request.user.groups.filter(name="exportgroup"):
485
- job = run_in_background(rfopenskin, "export_rf", export.pk)
486
- logger.debug("Export Fluoro to openSkin CSV job is {0}".format(job.id))
487
-
488
- return redirect(reverse_lazy("export"))
489
-
490
-
491
- @csrf_exempt
492
- @login_required
493
- def rf_xlsx_phe2019(request):
494
- """
495
- View to launch task to export fluoro studies to xlsx file in PHE 2019 IR/fluoro survey format
496
-
497
- :param request: Contains the database filtering parameters and user details.
498
- """
499
- import urllib
500
- from django.shortcuts import redirect
501
- from remapp.exports.rf_export import rf_phe_2019
502
-
503
- if request.user.groups.filter(name="exportgroup"):
504
- job = run_in_background(rf_phe_2019, "export_rf", request.GET, request.user.id)
505
- logger.debug(
506
- "Export PHE 2019 IR/fluoro survey format job is {0}.".format(job.id)
507
- )
508
- return redirect(reverse_lazy("export"))
509
- else:
510
- messages.error(request, "Only users in the Export group can launch exports")
511
- return redirect(
512
- "{0}?{1}".format(
513
- reverse_lazy("rf_summary_list_filter"), urllib.urlencode(request.GET)
514
- )
515
- )
516
-
517
-
518
- @csrf_exempt
519
- @login_required
520
- def mgcsv1(request, name=None, pat_id=None):
521
- """
522
- Launches export of mammo data to CSV
523
- :param request: Contains the database filtering parameters. Also used to get user group.
524
- :param name: string, 0 or 1 from URL indicating if names should be exported
525
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
526
- :return:
527
- """
528
- from django.shortcuts import redirect
529
- from remapp.exports.mg_export import exportMG2excel
530
-
531
- pid = include_pid(request, name, pat_id)
532
-
533
- if request.user.groups.filter(name="exportgroup"):
534
- job = run_in_background(
535
- exportMG2excel,
536
- "export_mg",
537
- request.GET,
538
- pid["pidgroup"],
539
- pid["include_names"],
540
- pid["include_pat_id"],
541
- request.user.id,
542
- )
543
- logger.debug("Export MG to CSV job is {0}".format(job.id))
544
-
545
- return redirect(reverse_lazy("export"))
546
-
547
-
548
- @csrf_exempt
549
- @login_required
550
- def mgxlsx1(request, name=None, pat_id=None):
551
- """
552
- Launches export of mammo data to xlsx
553
- :param request: Contains the database filtering parameters. Also used to get user group.
554
- :param name: string, 0 or 1 from URL indicating if names should be exported
555
- :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
556
- :return:
557
- """
558
- from django.shortcuts import redirect
559
- from remapp.exports.mg_export import exportMG2excel
560
-
561
- pid = include_pid(request, name, pat_id)
562
-
563
- if request.user.groups.filter(name="exportgroup"):
564
- job = run_in_background(
565
- exportMG2excel,
566
- "export_mg",
567
- request.GET,
568
- pid=pid["pidgroup"],
569
- name=pid["include_names"],
570
- patid=pid["include_pat_id"],
571
- user=request.user.id,
572
- xlsx=True,
573
- )
574
- logger.debug("Export MG to xlsx job is {0}".format(job.id))
575
-
576
- return redirect(reverse_lazy("export"))
577
-
578
-
579
- @csrf_exempt
580
- @login_required
581
- def mgnhsbsp(request):
582
- """
583
- View to launch task to export mammography studies to csv file using a NHSBSP template
584
-
585
- :param request: Contains the database filtering parameters. Also used to get user group.
586
- :type request: GET
587
- """
588
- from django.shortcuts import redirect
589
- from remapp.exports.mg_csv_nhsbsp import mg_csv_nhsbsp
590
-
591
- if request.user.groups.filter(name="exportgroup"):
592
- job = run_in_background(
593
- mg_csv_nhsbsp, "export_mg", request.GET, request.user.id
594
- )
595
- logger.debug("Export MG to CSV NHSBSP job is {0}".format(job.id))
596
-
597
- return redirect(reverse_lazy("export"))
598
-
599
-
600
- @csrf_exempt
601
- @login_required
602
- def export(request):
603
- """
604
- View to list current and completed exports to track progress, download and delete
605
-
606
- :param request: Used to get user group.
607
- """
608
- from remapp.models import Exports
609
-
610
- try:
611
- complete = Exports.objects.filter(status__contains="COMPLETE").order_by(
612
- "-export_date"
613
- )
614
- latest_complete_pk = complete[0].pk
615
- except IndexError:
616
- complete = None
617
- latest_complete_pk = 0
618
-
619
- admin = {
620
- "openremversion": remapp.__version__,
621
- "docsversion": remapp.__docs_version__,
622
- }
623
- for group in request.user.groups.all():
624
- admin[group.name] = True
625
- template = "remapp/exports.html"
626
-
627
- return render(
628
- request,
629
- template,
630
- {
631
- "admin": admin,
632
- "latest_complete_pk": latest_complete_pk,
633
- "complete": complete,
634
- },
635
- )
636
-
637
-
638
- @login_required
639
- def download(request, task_id):
640
- """
641
- View to handle downloads of files from the server
642
-
643
- Originally used for download of the export spreadsheets, now also used
644
- for downloading the patient size import logfiles.
645
-
646
- :param request: Used to get user group.
647
- :param task_id: ID of the export or logfile
648
-
649
- """
650
- import mimetypes
651
- from django.core.exceptions import ObjectDoesNotExist
652
- from wsgiref.util import FileWrapper
653
- from django.utils.encoding import smart_str
654
- from django.shortcuts import redirect
655
- from django.conf import settings
656
- from remapp.models import Exports
657
-
658
- exportperm = False
659
- pidperm = False
660
- if request.user.groups.filter(name="exportgroup"):
661
- exportperm = True
662
- if request.user.groups.filter(name="pidgroup"):
663
- pidperm = True
664
- try:
665
- exp = Exports.objects.get(task_id__exact=task_id)
666
- except ObjectDoesNotExist:
667
- messages.error(request, "Can't match the task ID, download aborted")
668
- return redirect(reverse_lazy("export"))
669
-
670
- if not exportperm:
671
- messages.error(request, "You don't have permission to download exported data")
672
- return redirect(reverse_lazy("export"))
673
-
674
- if exp.includes_pid and not pidperm:
675
- messages.error(
676
- request,
677
- "You don't have permission to download export data that includes patient identifiable information",
678
- )
679
- return redirect(reverse_lazy("export"))
680
-
681
- file_path = os.path.join(settings.MEDIA_ROOT, exp.filename.name)
682
- file_wrapper = FileWrapper(open(file_path, mode="rb"))
683
- file_mimetype = mimetypes.guess_type(file_path)
684
- response = HttpResponse(file_wrapper, content_type=file_mimetype)
685
- response["X-Sendfile"] = file_path
686
- response["Content-Length"] = os.stat(file_path).st_size
687
- response["Content-Disposition"] = "attachment; filename=%s" % smart_str(
688
- exp.filename
689
- )
690
- return response
691
-
692
-
693
- @csrf_exempt
694
- @login_required
695
- def deletefile(request):
696
- """
697
- View to delete export files from the server
698
-
699
- :param request: Contains the task ID
700
- :type request: POST
701
- """
702
- import sys
703
- from django.http import HttpResponseRedirect
704
- from django.urls import reverse
705
- from remapp.models import Exports
706
-
707
- for task in request.POST:
708
- exports = Exports.objects.filter(task_id__exact=request.POST[task])
709
- for export_object in exports:
710
- try:
711
- export_object.filename.delete()
712
- export_object.delete()
713
- messages.success(
714
- request, "Export file and database entry deleted successfully."
715
- )
716
- except OSError as e:
717
- messages.error(
718
- request,
719
- "Export file delete failed - please contact an administrator. Error({0}): {1}".format(
720
- e.errno, e.strerror
721
- ),
722
- )
723
- except Exception:
724
- messages.error(
725
- request,
726
- "Unexpected error - please contact an administrator: {0}".format(
727
- sys.exc_info()[0]
728
- ),
729
- )
730
-
731
- return HttpResponseRedirect(reverse(export))
732
-
733
-
734
- @login_required
735
- def export_abort(request, pk):
736
- """
737
- View to abort current export job
738
-
739
- :param request: Contains the task primary key
740
- :type request: POST
741
- """
742
- from django.http import HttpResponseRedirect
743
- from django.shortcuts import get_object_or_404
744
- from remapp.models import Exports, BackgroundTask
745
-
746
- export_task = get_object_or_404(Exports, pk=pk)
747
- task = get_object_or_404(BackgroundTask, uuid=export_task.task_id)
748
-
749
- if request.user.groups.filter(name="exportgroup"):
750
- terminate_background(task)
751
- export_task.delete()
752
- logger.info(
753
- "Export task {0} terminated from the Exports interface".format(
754
- export_task.task_id
755
- )
756
- )
757
-
758
- return HttpResponseRedirect(reverse_lazy("export"))
759
-
760
-
761
- @login_required
762
- def export_remove(request, task_id=None):
763
- """
764
- Function to remove export task from queue
765
-
766
- :param request: Contains the task primary key
767
- :param task_id: UUID of task in question
768
- :type request: POST
769
- """
770
- from django.http import HttpResponseRedirect
771
-
772
- if task_id and request.user.groups.filter(name="exportgroup"):
773
- remove_task_from_queue(task_id)
774
- logger.info("Export task {0} removed from queue".format(task_id))
775
-
776
- return HttpResponseRedirect(reverse_lazy("export"))
777
-
778
-
779
- @csrf_exempt
780
- @login_required
781
- def update_queue(request):
782
- """
783
- AJAX function to return queued exports
784
-
785
- :param request: Request object
786
- :return: HTML table of active exports
787
- """
788
- template = "remapp/exports-queue.html"
789
- if request.is_ajax():
790
- queued_export_tasks = get_queued_tasks(task_type="export")
791
- return render(request, template, {"queued": queued_export_tasks})
792
-
793
- return render(request, template)
794
-
795
-
796
- @csrf_exempt
797
- @login_required
798
- def update_active(request):
799
- """
800
- AJAX function to return active exports
801
-
802
- :param request: Request object
803
- :return: HTML table of active exports
804
- """
805
- from remapp.models import Exports
806
-
807
- if request.is_ajax():
808
- current_export_tasks = Exports.objects.filter(
809
- status__contains="CURRENT"
810
- ).order_by("-export_date")
811
- template = "remapp/exports-active.html"
812
-
813
- return render(request, template, {"current": current_export_tasks})
814
-
815
-
816
- @csrf_exempt
817
- @login_required
818
- def update_error(request):
819
- """
820
- AJAX function to return exports in error state
821
-
822
- :param request: Request object
823
- :return: HTML table of exports in error state
824
- """
825
- from remapp.models import Exports
826
-
827
- if request.is_ajax():
828
- error_export_tasks = Exports.objects.filter(status__contains="ERROR").order_by(
829
- "-export_date"
830
- )
831
- template = "remapp/exports-error.html"
832
-
833
- return render(request, template, {"errors": error_export_tasks})
834
-
835
-
836
- @csrf_exempt
837
- @login_required
838
- def update_complete(request):
839
- """
840
- AJAX function to return recently completed exports
841
-
842
- :param request: Request object, including pk of latest complete export at initial page load
843
- :return: HTML table of completed exports
844
- """
845
- from remapp.models import Exports
846
-
847
- if request.is_ajax():
848
- data = request.POST
849
- latest_complete_pk = data.get("latest_complete_pk")
850
- in_pid_group = data.get("in_pid_group")
851
- complete_export_tasks = Exports.objects.filter(
852
- status__contains="COMPLETE"
853
- ).filter(pk__gt=latest_complete_pk)
854
- template = "remapp/exports-complete.html"
855
-
856
- return render(
857
- request,
858
- template,
859
- {"complete": complete_export_tasks, "in_pid_group": in_pid_group},
860
- )
1
+ # OpenREM - Radiation Exposure Monitoring tools for the physicist
2
+ # Copyright (C) 2012,2013 The Royal Marsden NHS Foundation Trust
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # Additional permission under section 7 of GPLv3:
15
+ # You shall not make any use of the name of The Royal Marsden NHS
16
+ # Foundation trust in connection with this Program in any press or
17
+ # other public announcement without the prior written consent of
18
+ # The Royal Marsden NHS Foundation Trust.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
22
+
23
+ """
24
+ .. module:: exportviews.py
25
+ :synopsis: Module to render appropriate content according to request, specific to the exports.
26
+
27
+ .. moduleauthor:: Ed McDonagh
28
+
29
+ """
30
+
31
+ # Following two lines added so that sphinx autodocumentation works.
32
+ import os
33
+ import sys
34
+ import mimetypes
35
+ import urllib
36
+
37
+ import logging
38
+ from wsgiref.util import FileWrapper
39
+
40
+ from django.views.decorators.csrf import csrf_exempt
41
+ from django.contrib import messages
42
+ from django.contrib.auth.decorators import login_required
43
+ from django.http import HttpResponse, HttpResponseRedirect
44
+ from django.shortcuts import render, get_object_or_404, redirect
45
+ from django.urls import reverse_lazy, reverse
46
+ from django.conf import settings
47
+ from django.core.exceptions import ObjectDoesNotExist
48
+ from django.utils.encoding import smart_str
49
+ from django.db.models import Max
50
+
51
+ from ..models import Exports, BackgroundTask, GeneralStudyModuleAttr
52
+ from ..interface.mod_filters import dx_acq_filter
53
+
54
+ from .ct_export import ct_phe_2019, ctxlsx, ct_csv
55
+ from .dx_export import dx_phe_2019, dxxlsx, exportDX2excel
56
+ from .mg_csv_nhsbsp import mg_csv_nhsbsp
57
+ from .mg_export import mgxlsx, exportMG2csv
58
+ from .nm_export import nmxlsx, exportNM2csv
59
+ from .rf_export import rf_phe_2019, rfopenskin_csv, rfxlsx, exportFL2excel
60
+
61
+ from ..tools.background import ( # pylint: disable=wrong-import-position
62
+ run_in_background,
63
+ terminate_background,
64
+ get_queued_tasks,
65
+ remove_task_from_queue,
66
+ )
67
+
68
+ from ..version import __version__, __docs_version__
69
+
70
+ os.environ["DJANGO_SETTINGS_MODULE"] = "openremproject.settings"
71
+
72
+ logger = logging.getLogger(__name__)
73
+
74
+
75
+ def include_pid(request, name, pat_id):
76
+ """
77
+ Check if user is allowed to export PID, then check if they have asked to.
78
+ :param request: request so we can determine the user and therefore groups
79
+ :param name: string, 0 or 1 from URL indicating if names should be exported
80
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
81
+ :return: dict, with pidgroup, include_names and include_pat_id as bools
82
+ """
83
+ pid = bool(request.user.groups.filter(name="pidgroup"))
84
+
85
+ include_names = False
86
+ include_pat_id = False
87
+ if pid:
88
+ try:
89
+ if int(name): # Will be unicode from URL
90
+ include_names = True
91
+ except ValueError: # If anything else comes in, just don't export that column
92
+ pass
93
+ try:
94
+ if int(pat_id):
95
+ include_pat_id = True
96
+ except ValueError:
97
+ pass
98
+
99
+ return {
100
+ "pidgroup": pid,
101
+ "include_names": include_names,
102
+ "include_pat_id": include_pat_id,
103
+ }
104
+
105
+
106
+ @csrf_exempt
107
+ @login_required
108
+ def ctcsv1(request, name=None, pat_id=None):
109
+ """
110
+ View to launch task to export CT studies to csv file
111
+
112
+ :param request: Contains the database filtering parameters. Also used to get user group.
113
+ :param name: string, 0 or 1 from URL indicating if names should be exported
114
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
115
+ :type request: GET
116
+ """
117
+ pid = include_pid(request, name, pat_id)
118
+
119
+ if request.user.groups.filter(name="exportgroup"):
120
+ # The 'ct_acquisition_type' filter values must be passed to ct_csv as a list, so convert the GET to a dict and
121
+ # then update the 'ct_acquisition_type' value with a list.
122
+ filter_dict = request.GET.dict()
123
+ if "ct_acquisition_type" in filter_dict:
124
+ filter_dict["ct_acquisition_type"] = request.GET.getlist(
125
+ "ct_acquisition_type"
126
+ )
127
+
128
+ job = run_in_background(
129
+ ct_csv,
130
+ "export_ct",
131
+ filter_dict,
132
+ pid["pidgroup"],
133
+ pid["include_names"],
134
+ pid["include_pat_id"],
135
+ request.user.id,
136
+ )
137
+ logger.debug(f"Export CT to CSV job is {job.id}")
138
+ return redirect(reverse_lazy("export"))
139
+
140
+
141
+ @csrf_exempt
142
+ @login_required
143
+ def ctxlsx1(request, name=None, pat_id=None):
144
+ """
145
+ View to launch task to export CT studies to xlsx file
146
+
147
+ :param request: Contains the database filtering parameters. Also used to get user group.
148
+ :param name: string, 0 or 1 from URL indicating if names should be exported
149
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
150
+ :type request: GET
151
+ """
152
+ pid = include_pid(request, name, pat_id)
153
+
154
+ if request.user.groups.filter(name="exportgroup"):
155
+ # The 'ct_acquisition_type' filter values must be passed to ctxlsx as a list, so convert the GET to a dict and
156
+ # then update the 'ct_acquisition_type' value with a list.
157
+ filter_dict = request.GET.dict()
158
+ if "ct_acquisition_type" in filter_dict:
159
+ filter_dict["ct_acquisition_type"] = request.GET.getlist(
160
+ "ct_acquisition_type"
161
+ )
162
+
163
+ job = run_in_background(
164
+ ctxlsx,
165
+ "export_ct",
166
+ filter_dict,
167
+ pid["pidgroup"],
168
+ pid["include_names"],
169
+ pid["include_pat_id"],
170
+ request.user.id,
171
+ )
172
+ logger.debug("Export CT to XLSX job is {0}".format(job.id))
173
+
174
+ return redirect(reverse_lazy("export"))
175
+
176
+
177
+ @csrf_exempt
178
+ @login_required
179
+ def ct_xlsx_phe2019(request):
180
+ """
181
+ View to launch task to export CT studies to xlsx file in PHE 2019 CT survey format
182
+
183
+ :param request: Contains the database filtering parameters and user details.
184
+ """
185
+ if request.user.groups.filter(name="exportgroup"):
186
+ job = run_in_background(ct_phe_2019, "export_ct", request.GET, request.user.id)
187
+ logger.debug("Export CT to XLSX job is {0}".format(job.id))
188
+ return redirect(reverse_lazy("export"))
189
+
190
+
191
+ @csrf_exempt
192
+ @login_required
193
+ def nmcsv1(request, name=None, pat_id=None):
194
+ """
195
+ View to launch task to export NM studies to csv file
196
+
197
+ :param request: Contains the database filtering parameters. Also used to get user group.
198
+ :param name: string, 0 or 1 from URL indicating if names should be exported
199
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
200
+ :type request: GET
201
+ """
202
+ pid = include_pid(request, name, pat_id)
203
+
204
+ if request.user.groups.filter(name="exportgroup"):
205
+ job = run_in_background(
206
+ exportNM2csv,
207
+ "export_nm",
208
+ request.GET,
209
+ pid["pidgroup"],
210
+ pid["include_names"],
211
+ pid["include_pat_id"],
212
+ request.user.id,
213
+ )
214
+ logger.debug(f"Export NM to CSV job is {job.id}")
215
+
216
+ return redirect(reverse_lazy("export"))
217
+
218
+
219
+ @csrf_exempt
220
+ @login_required
221
+ def nmxlsx1(request, name=None, pat_id=None):
222
+ """
223
+ View to launch celery task to export NM studies to excel file
224
+
225
+ :param request: Contains the database filtering parameters. Also used to get user group.
226
+ :param name: string, 0 or 1 from URL indicating if names should be exported
227
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
228
+ :type request: GET
229
+ """
230
+ pid = include_pid(request, name, pat_id)
231
+
232
+ if request.user.groups.filter(name="exportgroup"):
233
+ filter_dict = request.GET.dict()
234
+ if "nm_acquisition_type" in filter_dict:
235
+ filter_dict["nm_acquisition_type"] = request.GET.getlist(
236
+ "nm_acquisition_type"
237
+ )
238
+
239
+ job = run_in_background(
240
+ nmxlsx,
241
+ "export_nm",
242
+ filter_dict,
243
+ pid["pidgroup"],
244
+ pid["include_names"],
245
+ pid["include_pat_id"],
246
+ request.user.id,
247
+ )
248
+ logger.debug(f"Exprt NM to Excel job is {job.id}")
249
+
250
+ return redirect(reverse_lazy("export"))
251
+
252
+
253
+ @csrf_exempt
254
+ @login_required
255
+ def dxcsv1(request, name=None, pat_id=None):
256
+ """
257
+ View to launch task to export DX and CR studies to csv file
258
+
259
+ :param request: Contains the database filtering parameters. Also used to get user group.
260
+ :param name: string, 0 or 1 from URL indicating if names should be exported
261
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
262
+ :type request: GET
263
+ """
264
+ pid = include_pid(request, name, pat_id)
265
+
266
+ if request.user.groups.filter(name="exportgroup"):
267
+ job = run_in_background(
268
+ exportDX2excel,
269
+ "export_dx",
270
+ request.GET,
271
+ pid["pidgroup"],
272
+ pid["include_names"],
273
+ pid["include_pat_id"],
274
+ request.user.id,
275
+ )
276
+ logger.debug("Export DX to CSV job is {0}".format(job.id))
277
+
278
+ return redirect(reverse_lazy("export"))
279
+
280
+
281
+ @csrf_exempt
282
+ @login_required
283
+ def dxxlsx1(request, name=None, pat_id=None):
284
+ """
285
+ View to launch task to export DX and CR studies to xlsx file
286
+
287
+ :param request: Contains the database filtering parameters. Also used to get user group.
288
+ :param name: string, 0 or 1 from URL indicating if names should be exported
289
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
290
+ :type request: GET
291
+ """
292
+ pid = include_pid(request, name, pat_id)
293
+
294
+ if request.user.groups.filter(name="exportgroup"):
295
+ job = run_in_background(
296
+ dxxlsx,
297
+ "export_dx",
298
+ request.GET,
299
+ pid["pidgroup"],
300
+ pid["include_names"],
301
+ pid["include_pat_id"],
302
+ request.user.id,
303
+ )
304
+ logger.debug("Export DX to XLSX job is {0}".format(job.id))
305
+
306
+ return redirect(reverse_lazy("export"))
307
+
308
+
309
+ @csrf_exempt
310
+ @login_required
311
+ def dx_xlsx_phe2019(request, export_type=None):
312
+ """
313
+ View to launch task to export DX studies to xlsx file in PHE 2019 DX survey format
314
+
315
+ :param request: Contains the database filtering parameters and user details.
316
+ :param export_type: string, 'projection' or 'exam'
317
+ """
318
+ if request.user.groups.filter(name="exportgroup"):
319
+ if export_type in ("exam", "projection"):
320
+ bespoke = False
321
+ exams = dx_acq_filter(request.GET, pid=False).qs
322
+ if not exams.count():
323
+ messages.error(request, "No studies in export, nothing to do!")
324
+ return redirect(
325
+ "{0}?{1}".format(
326
+ reverse_lazy("dx_summary_list_filter"),
327
+ urllib.urlencode(request.GET),
328
+ )
329
+ )
330
+ max_events_dict = exams.aggregate(Max("number_of_events"))
331
+ max_events = max_events_dict["number_of_events__max"]
332
+ if "projection" in export_type:
333
+ if max_events > 1:
334
+ messages.warning(
335
+ request,
336
+ "PHE 2019 DX Projection export is expecting one exposure per study - "
337
+ "some studies selected have more than one. Only the first exposure will "
338
+ "be considered.",
339
+ )
340
+ else:
341
+ messages.info(
342
+ request, "PHE 2019 DX single projection export started."
343
+ )
344
+ job = run_in_background(
345
+ dx_phe_2019,
346
+ "export_dx",
347
+ request.GET,
348
+ request.user.id,
349
+ projection=True,
350
+ )
351
+ logger.debug(
352
+ "Export PHE 2019 DX survey format job is {0}".format(job.id)
353
+ )
354
+ return redirect(reverse_lazy("export"))
355
+ elif "exam" in export_type:
356
+ if max_events > 6:
357
+ bespoke = True
358
+ if max_events > 20:
359
+ messages.warning(
360
+ request,
361
+ "PHE 2019 DX Study sheets expect a maximum of six projections. You "
362
+ "need to request a bespoke workbook from PHE. This export has a "
363
+ "maximum of {0} projections, but only the first 20 will be included "
364
+ "in the main columns of the bespoke worksheet.".format(
365
+ max_events
366
+ ),
367
+ )
368
+ else:
369
+ messages.warning(
370
+ request,
371
+ "PHE 2019 DX Study sheets expect a maximum of six projections. This "
372
+ "export has a maximum of {0} projections so you will need to request"
373
+ " a bespoke workbook from PHE. This has space for 20 "
374
+ "projections.".format(max_events),
375
+ )
376
+ else:
377
+ messages.info(request, "PHE 2019 DX Study export started.")
378
+ job = run_in_background(
379
+ dx_phe_2019,
380
+ "export_dx",
381
+ request.GET,
382
+ request.user.id,
383
+ projection=False,
384
+ bespoke=bespoke,
385
+ )
386
+ logger.debug(
387
+ "Export PHE 2019 DX survey format job is {0}".format(job.id)
388
+ )
389
+ return redirect(reverse_lazy("export"))
390
+ else:
391
+ messages.error(request, "Malformed export URL {0}".format(type))
392
+ return redirect(
393
+ "{0}?{1}".format(
394
+ reverse_lazy("dx_summary_list_filter"),
395
+ urllib.urlencode(request.GET),
396
+ )
397
+ )
398
+ else:
399
+ messages.error(request, "Only users in the Export group can launch exports")
400
+ return redirect(
401
+ "{0}?{1}".format(
402
+ reverse_lazy("dx_summary_list_filter"), urllib.urlencode(request.GET)
403
+ )
404
+ )
405
+
406
+
407
+ @csrf_exempt
408
+ @login_required
409
+ def flcsv1(request, name=None, pat_id=None):
410
+ """
411
+ View to launch task to export fluoroscopy studies to csv file
412
+
413
+ :param request: Contains the database filtering parameters. Also used to get user group.
414
+ :param name: string, 0 or 1 from URL indicating if names should be exported
415
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
416
+ :type request: GET
417
+ """
418
+ pid = include_pid(request, name, pat_id)
419
+
420
+ if request.user.groups.filter(name="exportgroup"):
421
+ job = run_in_background(
422
+ exportFL2excel,
423
+ "export_fl",
424
+ request.GET,
425
+ pid["pidgroup"],
426
+ pid["include_names"],
427
+ pid["include_pat_id"],
428
+ request.user.id,
429
+ )
430
+ logger.debug("Export Fluoro to CSV job is {0}".format(job.id))
431
+
432
+ return redirect(reverse_lazy("export"))
433
+
434
+
435
+ @csrf_exempt
436
+ @login_required
437
+ def rfxlsx1(request, name=None, pat_id=None):
438
+ """
439
+ View to launch task to export fluoroscopy studies to xlsx file
440
+
441
+ :param request: Contains the database filtering parameters. Also used to get user group.
442
+ :param name: string, 0 or 1 from URL indicating if names should be exported
443
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
444
+ :type request: GET
445
+ """
446
+ pid = include_pid(request, name, pat_id)
447
+
448
+ if request.user.groups.filter(name="exportgroup"):
449
+ job = run_in_background(
450
+ rfxlsx,
451
+ "export_rf",
452
+ request.GET,
453
+ pid["pidgroup"],
454
+ pid["include_names"],
455
+ pid["include_pat_id"],
456
+ request.user.id,
457
+ )
458
+ logger.debug("Export Fluoro to XLSX job is {0}".format(job.id))
459
+
460
+ return redirect(reverse_lazy("export"))
461
+
462
+
463
+ @csrf_exempt
464
+ @login_required
465
+ def rfopenskin(request, pk):
466
+ """
467
+ Create csv export suitable for import to standalone openSkin
468
+ :param request: request object
469
+ :param pk: primary key of study in GeneralStudyModuleAttr table
470
+ """
471
+ export = get_object_or_404(GeneralStudyModuleAttr, pk=pk)
472
+
473
+ if request.user.groups.filter(name="exportgroup"):
474
+ job = run_in_background(rfopenskin_csv, "export_rf", export.pk)
475
+ logger.debug("Export Fluoro to openSkin CSV job is {0}".format(job.id))
476
+
477
+ return redirect(reverse_lazy("export"))
478
+
479
+
480
+ @csrf_exempt
481
+ @login_required
482
+ def rf_xlsx_phe2019(request):
483
+ """
484
+ View to launch task to export fluoro studies to xlsx file in PHE 2019 IR/fluoro survey format
485
+
486
+ :param request: Contains the database filtering parameters and user details.
487
+ """
488
+ if request.user.groups.filter(name="exportgroup"):
489
+ job = run_in_background(rf_phe_2019, "export_rf", request.GET, request.user.id)
490
+ logger.debug(
491
+ "Export PHE 2019 IR/fluoro survey format job is {0}.".format(job.id)
492
+ )
493
+ return redirect(reverse_lazy("export"))
494
+ else:
495
+ messages.error(request, "Only users in the Export group can launch exports")
496
+ return redirect(
497
+ "{0}?{1}".format(
498
+ reverse_lazy("rf_summary_list_filter"), urllib.urlencode(request.GET)
499
+ )
500
+ )
501
+
502
+
503
+ @csrf_exempt
504
+ @login_required
505
+ def mgcsv1(request, name=None, pat_id=None):
506
+ """
507
+ Launches export of mammo data to CSV
508
+ :param request: Contains the database filtering parameters. Also used to get user group.
509
+ :param name: string, 0 or 1 from URL indicating if names should be exported
510
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
511
+ :return:
512
+ """
513
+ pid = include_pid(request, name, pat_id)
514
+
515
+ if request.user.groups.filter(name="exportgroup"):
516
+ job = run_in_background(
517
+ exportMG2csv,
518
+ "export_mg",
519
+ request.GET,
520
+ pid["pidgroup"],
521
+ pid["include_names"],
522
+ pid["include_pat_id"],
523
+ request.user.id,
524
+ )
525
+ logger.debug("Export MG to CSV job is {0}".format(job.id))
526
+
527
+ return redirect(reverse_lazy("export"))
528
+
529
+
530
+ @csrf_exempt
531
+ @login_required
532
+ def mgxlsx1(request, name=None, pat_id=None):
533
+ """
534
+ Launches export of mammo data to xlsx
535
+ :param request: Contains the database filtering parameters. Also used to get user group.
536
+ :param name: string, 0 or 1 from URL indicating if names should be exported
537
+ :param pat_id: string, 0 or 1 from URL indicating if patient ID should be exported
538
+ :return:
539
+ """
540
+ pid = include_pid(request, name, pat_id)
541
+
542
+ if request.user.groups.filter(name="exportgroup"):
543
+
544
+ filter_dict = request.GET.dict()
545
+ if "mg_acquisition_type" in filter_dict:
546
+ filter_dict["mg_acquisition_type"] = request.GET.getlist(
547
+ "mg_acquisition_type"
548
+ )
549
+
550
+ job = run_in_background(
551
+ mgxlsx,
552
+ "export_mg",
553
+ filter_dict,
554
+ pid["pidgroup"],
555
+ pid["include_names"],
556
+ pid["include_pat_id"],
557
+ request.user.id,
558
+ )
559
+ logger.debug("Export MG to xlsx job is {0}".format(job.id))
560
+
561
+ return redirect(reverse_lazy("export"))
562
+
563
+
564
+ @csrf_exempt
565
+ @login_required
566
+ def mgnhsbsp(request):
567
+ """
568
+ View to launch task to export mammography studies to csv file using a NHSBSP template
569
+
570
+ :param request: Contains the database filtering parameters. Also used to get user group.
571
+ :type request: GET
572
+ """
573
+ if request.user.groups.filter(name="exportgroup"):
574
+ job = run_in_background(
575
+ mg_csv_nhsbsp, "export_mg", request.GET, request.user.id
576
+ )
577
+ logger.debug("Export MG to CSV NHSBSP job is {0}".format(job.id))
578
+
579
+ return redirect(reverse_lazy("export"))
580
+
581
+
582
+ @csrf_exempt
583
+ @login_required
584
+ def export(request):
585
+ """
586
+ View to list current and completed exports to track progress, download and delete
587
+
588
+ :param request: Used to get user group.
589
+ """
590
+ try:
591
+ complete = Exports.objects.filter(status__contains="COMPLETE").order_by(
592
+ "-export_date"
593
+ )
594
+ latest_complete_pk = complete[0].pk
595
+ except IndexError:
596
+ complete = None
597
+ latest_complete_pk = 0
598
+
599
+ admin = {
600
+ "openremversion": __version__,
601
+ "docsversion": __docs_version__,
602
+ }
603
+ for group in request.user.groups.all():
604
+ admin[group.name] = True
605
+ template = "remapp/exports.html"
606
+
607
+ return render(
608
+ request,
609
+ template,
610
+ {
611
+ "admin": admin,
612
+ "latest_complete_pk": latest_complete_pk,
613
+ "complete": complete,
614
+ },
615
+ )
616
+
617
+
618
+ @login_required
619
+ def download(request, task_id):
620
+ """
621
+ View to handle downloads of files from the server
622
+
623
+ Originally used for download of the export spreadsheets, now also used
624
+ for downloading the patient size import logfiles.
625
+
626
+ :param request: Used to get user group.
627
+ :param task_id: ID of the export or logfile
628
+
629
+ """
630
+ exportperm = False
631
+ pidperm = False
632
+ if request.user.groups.filter(name="exportgroup"):
633
+ exportperm = True
634
+ if request.user.groups.filter(name="pidgroup"):
635
+ pidperm = True
636
+ try:
637
+ exp = Exports.objects.get(task_id__exact=task_id)
638
+ except ObjectDoesNotExist:
639
+ messages.error(request, "Can't match the task ID, download aborted")
640
+ return redirect(reverse_lazy("export"))
641
+
642
+ if not exportperm:
643
+ messages.error(request, "You don't have permission to download exported data")
644
+ return redirect(reverse_lazy("export"))
645
+
646
+ if exp.includes_pid and not pidperm:
647
+ messages.error(
648
+ request,
649
+ "You don't have permission to download export data that includes patient identifiable information",
650
+ )
651
+ return redirect(reverse_lazy("export"))
652
+
653
+ file_path = os.path.join(settings.MEDIA_ROOT, exp.filename.name)
654
+ with open(file_path, mode="rb") as f:
655
+ file_wrapper = FileWrapper(f)
656
+ file_mimetype = mimetypes.guess_type(file_path)
657
+ response = HttpResponse(file_wrapper, content_type=file_mimetype)
658
+ response["X-Sendfile"] = file_path
659
+ response["Content-Length"] = os.stat(file_path).st_size
660
+ response["Content-Disposition"] = "attachment; filename=%s" % smart_str(
661
+ exp.filename
662
+ ) # pylint: disable=consider-using-f-string
663
+ return response
664
+
665
+
666
+ @csrf_exempt
667
+ @login_required
668
+ def deletefile(request):
669
+ """
670
+ View to delete export files from the server
671
+
672
+ :param request: Contains the task ID
673
+ :type request: POST
674
+ """
675
+ for task in request.POST:
676
+ exports = Exports.objects.filter(task_id__exact=request.POST[task])
677
+ for export_object in exports:
678
+ try:
679
+ export_object.filename.delete()
680
+ export_object.delete()
681
+ messages.success(
682
+ request, "Export file and database entry deleted successfully."
683
+ )
684
+ except OSError as e:
685
+ messages.error(
686
+ request,
687
+ f"Export file delete failed - please contact an administrator. Error({e.errno}): {e.strerror}", # pylint: disable=line-too-long
688
+ )
689
+ except Exception:
690
+ messages.error(
691
+ request,
692
+ f"Unexpected error - please contact an administrator: {sys.exc_info()[0]}",
693
+ )
694
+
695
+ return HttpResponseRedirect(reverse(export))
696
+
697
+
698
+ @login_required
699
+ def export_abort(request, pk):
700
+ """
701
+ View to abort current export job
702
+
703
+ :param request: Contains the task primary key
704
+ :type request: POST
705
+ """
706
+ if pk and request.user.groups.filter(name="exportgroup"):
707
+ export_task = get_object_or_404(Exports, pk=pk)
708
+ try:
709
+ task = BackgroundTask.objects.get(uuid=export_task.task_id)
710
+ terminate_background(task)
711
+ except ObjectDoesNotExist:
712
+ pass
713
+ export_task.delete()
714
+ logger.info(
715
+ f"Export task {export_task.task_id} terminated from the Exports interface"
716
+ )
717
+ return HttpResponseRedirect(reverse_lazy("export"))
718
+
719
+
720
+ @login_required
721
+ def export_remove(request, task_id=None):
722
+ """
723
+ Function to remove export task from queue
724
+
725
+ :param request: Contains the task primary key
726
+ :param task_id: UUID of task in question
727
+ :type request: POST
728
+ """
729
+ if task_id and request.user.groups.filter(name="exportgroup"):
730
+ remove_task_from_queue(task_id)
731
+ logger.info(f"Export task {task_id} removed from queue")
732
+
733
+ return HttpResponseRedirect(reverse_lazy("export"))
734
+
735
+
736
+ @csrf_exempt
737
+ @login_required
738
+ def update_queue(request):
739
+ """
740
+ AJAX function to return queued exports
741
+
742
+ :param request: Request object
743
+ :return: HTML table of active exports
744
+ """
745
+ template = "remapp/exports-queue.html"
746
+ if request.is_ajax():
747
+ queued_export_tasks = get_queued_tasks(task_type="export")
748
+ return render(request, template, {"queued": queued_export_tasks})
749
+
750
+ return render(request, template)
751
+
752
+
753
+ @csrf_exempt
754
+ @login_required
755
+ def update_active(request):
756
+ """
757
+ AJAX function to return active exports
758
+
759
+ :param request: Request object
760
+ :return: HTML table of active exports
761
+ """
762
+ template = "remapp/exports-active.html"
763
+ if request.is_ajax():
764
+ current_export_tasks = Exports.objects.filter(
765
+ status__contains="CURRENT"
766
+ ).order_by("-export_date")
767
+ return render(request, template, {"current": current_export_tasks})
768
+
769
+ return render(request, template)
770
+
771
+
772
+ @csrf_exempt
773
+ @login_required
774
+ def update_error(request):
775
+ """
776
+ AJAX function to return exports in error state
777
+
778
+ :param request: Request object
779
+ :return: HTML table of exports in error state
780
+ """
781
+ template = "remapp/exports-error.html"
782
+ if request.is_ajax():
783
+ error_export_tasks = Exports.objects.filter(status__contains="ERROR").order_by(
784
+ "-export_date"
785
+ )
786
+ return render(request, template, {"errors": error_export_tasks})
787
+
788
+ return render(request, template)
789
+
790
+
791
+ @csrf_exempt
792
+ @login_required
793
+ def update_complete(request):
794
+ """
795
+ AJAX function to return recently completed exports
796
+
797
+ :param request: Request object, including pk of latest complete export at initial page load
798
+ :return: HTML table of completed exports
799
+ """
800
+ template = "remapp/exports-complete.html"
801
+ if request.is_ajax():
802
+ data = request.POST
803
+ latest_complete_pk = data.get("latest_complete_pk")
804
+ in_pid_group = data.get("in_pid_group")
805
+ complete_export_tasks = Exports.objects.filter(
806
+ status__contains="COMPLETE"
807
+ ).filter(pk__gt=latest_complete_pk)
808
+
809
+ return render(
810
+ request,
811
+ template,
812
+ {"complete": complete_export_tasks, "in_pid_group": in_pid_group},
813
+ )
814
+
815
+ return render(request, template)