endoreg-db 0.8.4.4__py3-none-any.whl → 0.8.8.0__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.

Potentially problematic release.


This version of endoreg-db might be problematic. Click here for more details.

Files changed (372) hide show
  1. endoreg_db/authz/auth.py +74 -0
  2. endoreg_db/authz/backends.py +168 -0
  3. endoreg_db/authz/management/commands/list_routes.py +18 -0
  4. endoreg_db/authz/middleware.py +83 -0
  5. endoreg_db/authz/permissions.py +127 -0
  6. endoreg_db/authz/policy.py +218 -0
  7. endoreg_db/authz/views_auth.py +66 -0
  8. endoreg_db/config/env.py +13 -8
  9. endoreg_db/data/__init__.py +8 -31
  10. endoreg_db/data/_examples/disease.yaml +55 -0
  11. endoreg_db/data/_examples/disease_classification.yaml +13 -0
  12. endoreg_db/data/_examples/disease_classification_choice.yaml +62 -0
  13. endoreg_db/data/_examples/event.yaml +64 -0
  14. endoreg_db/data/_examples/examination.yaml +72 -0
  15. endoreg_db/data/_examples/finding/anatomy_colon.yaml +128 -0
  16. endoreg_db/data/_examples/finding/colonoscopy.yaml +40 -0
  17. endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +56 -0
  18. endoreg_db/data/_examples/finding/complication.yaml +16 -0
  19. endoreg_db/data/_examples/finding/data.yaml +105 -0
  20. endoreg_db/data/_examples/finding/examination_setting.yaml +16 -0
  21. endoreg_db/data/_examples/finding/medication_related.yaml +18 -0
  22. endoreg_db/data/_examples/finding/outcome.yaml +12 -0
  23. endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +68 -0
  24. endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +22 -0
  25. endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +25 -0
  26. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
  27. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
  28. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +68 -0
  29. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
  30. endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +80 -0
  31. endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +21 -0
  32. endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +20 -0
  33. endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +26 -0
  34. endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +22 -0
  35. endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +53 -0
  36. endoreg_db/data/_examples/finding_classification/complication_generic.yaml +25 -0
  37. endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +40 -0
  38. endoreg_db/data/_examples/finding_classification/histology_colo.yaml +51 -0
  39. endoreg_db/data/_examples/finding_classification/intervention_required.yaml +26 -0
  40. endoreg_db/data/_examples/finding_classification/medication_related.yaml +23 -0
  41. endoreg_db/data/_examples/finding_classification/visualized.yaml +33 -0
  42. endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +78 -0
  43. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +32 -0
  44. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
  45. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
  46. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +15 -0
  47. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +17 -0
  48. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_paris.yaml +57 -0
  49. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +49 -0
  50. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +14 -0
  51. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +36 -0
  52. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_location.yaml +229 -0
  53. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
  54. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +82 -0
  55. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
  56. endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +15 -0
  57. endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
  58. endoreg_db/data/_examples/finding_classification_choice/histology.yaml +24 -0
  59. endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +20 -0
  60. endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +19 -0
  61. endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +11 -0
  62. endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +48 -0
  63. endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +43 -0
  64. endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +168 -0
  65. endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +128 -0
  66. endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +32 -0
  67. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +9 -0
  68. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +36 -0
  69. endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +15 -0
  70. endoreg_db/data/_examples/finding_type/data.yaml +43 -0
  71. endoreg_db/data/_examples/requirement/age.yaml +26 -0
  72. endoreg_db/data/_examples/requirement/colonoscopy_baseline_austria.yaml +45 -0
  73. endoreg_db/data/_examples/requirement/disease_cardiovascular.yaml +79 -0
  74. endoreg_db/data/_examples/requirement/disease_classification_choice_cardiovascular.yaml +41 -0
  75. endoreg_db/data/_examples/requirement/disease_hepatology.yaml +12 -0
  76. endoreg_db/data/_examples/requirement/disease_misc.yaml +12 -0
  77. endoreg_db/data/_examples/requirement/disease_renal.yaml +96 -0
  78. endoreg_db/data/_examples/requirement/endoscopy_bleeding_risk.yaml +59 -0
  79. endoreg_db/data/_examples/requirement/event_cardiology.yaml +251 -0
  80. endoreg_db/data/_examples/requirement/event_requirements.yaml +145 -0
  81. endoreg_db/data/_examples/requirement/finding_colon_polyp.yaml +50 -0
  82. endoreg_db/data/_examples/requirement/gender.yaml +25 -0
  83. endoreg_db/data/_examples/requirement/lab_value.yaml +441 -0
  84. endoreg_db/data/_examples/requirement/medication.yaml +93 -0
  85. endoreg_db/data/_examples/requirement_operator/age.yaml +13 -0
  86. endoreg_db/data/_examples/requirement_operator/lab_operators.yaml +129 -0
  87. endoreg_db/data/_examples/requirement_operator/model_operators.yaml +96 -0
  88. endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +48 -0
  89. endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +57 -0
  90. endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
  91. endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +4 -3
  92. endoreg_db/data/event_classification/data.yaml +4 -0
  93. endoreg_db/data/event_classification_choice/data.yaml +9 -0
  94. endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +43 -70
  95. endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +22 -52
  96. endoreg_db/data/finding_classification/colonoscopy_location.yaml +31 -62
  97. endoreg_db/data/finding_classification/histology_colo.yaml +28 -36
  98. endoreg_db/data/requirement/colon_polyp_intervention.yaml +49 -0
  99. endoreg_db/data/requirement/coloreg_colon_polyp.yaml +49 -0
  100. endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +31 -12
  101. endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
  102. endoreg_db/data/requirement_set/02_endoscopy_bleeding_risk.yaml +46 -0
  103. endoreg_db/data/requirement_set/90_coloreg.yaml +178 -0
  104. endoreg_db/data/requirement_set/_old_ +109 -0
  105. endoreg_db/data/requirement_set_type/data.yaml +21 -0
  106. endoreg_db/data/setup_config.yaml +4 -4
  107. endoreg_db/data/tag/requirement_set_tags.yaml +21 -0
  108. endoreg_db/exceptions.py +5 -2
  109. endoreg_db/helpers/data_loader.py +1 -1
  110. endoreg_db/management/commands/create_model_meta_from_huggingface.py +21 -10
  111. endoreg_db/management/commands/create_multilabel_model_meta.py +299 -129
  112. endoreg_db/management/commands/import_video.py +9 -10
  113. endoreg_db/management/commands/import_video_with_classification.py +1 -1
  114. endoreg_db/management/commands/init_default_ai_model.py +1 -1
  115. endoreg_db/management/commands/list_routes.py +18 -0
  116. endoreg_db/management/commands/load_ai_model_data.py +2 -1
  117. endoreg_db/management/commands/load_center_data.py +12 -12
  118. endoreg_db/management/commands/load_requirement_data.py +60 -31
  119. endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
  120. endoreg_db/management/commands/setup_endoreg_db.py +14 -10
  121. endoreg_db/management/commands/storage_management.py +271 -203
  122. endoreg_db/migrations/0001_initial.py +1799 -1300
  123. endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
  124. endoreg_db/migrations/_old/0001_initial.py +1857 -0
  125. endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
  126. endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
  127. endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
  128. endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
  129. endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
  130. endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
  131. endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
  132. endoreg_db/models/__init__.py +78 -123
  133. endoreg_db/models/administration/__init__.py +21 -42
  134. endoreg_db/models/administration/ai/active_model.py +2 -2
  135. endoreg_db/models/administration/ai/ai_model.py +7 -6
  136. endoreg_db/models/administration/case/__init__.py +1 -15
  137. endoreg_db/models/administration/case/case.py +3 -3
  138. endoreg_db/models/administration/case/case_template/__init__.py +2 -14
  139. endoreg_db/models/administration/case/case_template/case_template.py +2 -124
  140. endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
  141. endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
  142. endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
  143. endoreg_db/models/administration/center/center.py +33 -19
  144. endoreg_db/models/administration/center/center_product.py +12 -9
  145. endoreg_db/models/administration/center/center_resource.py +25 -19
  146. endoreg_db/models/administration/center/center_shift.py +21 -17
  147. endoreg_db/models/administration/center/center_waste.py +16 -8
  148. endoreg_db/models/administration/person/__init__.py +2 -0
  149. endoreg_db/models/administration/person/employee/employee.py +10 -5
  150. endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
  151. endoreg_db/models/administration/person/employee/employee_type.py +12 -6
  152. endoreg_db/models/administration/person/examiner/examiner.py +13 -11
  153. endoreg_db/models/administration/person/patient/__init__.py +2 -0
  154. endoreg_db/models/administration/person/patient/patient.py +103 -100
  155. endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
  156. endoreg_db/models/administration/person/person.py +4 -0
  157. endoreg_db/models/administration/person/profession/__init__.py +8 -4
  158. endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
  159. endoreg_db/models/administration/product/product.py +20 -15
  160. endoreg_db/models/administration/product/product_material.py +17 -18
  161. endoreg_db/models/administration/product/product_weight.py +12 -8
  162. endoreg_db/models/administration/product/reference_product.py +23 -55
  163. endoreg_db/models/administration/qualification/qualification.py +7 -3
  164. endoreg_db/models/administration/qualification/qualification_type.py +7 -3
  165. endoreg_db/models/administration/shift/scheduled_days.py +8 -5
  166. endoreg_db/models/administration/shift/shift.py +16 -12
  167. endoreg_db/models/administration/shift/shift_type.py +23 -31
  168. endoreg_db/models/label/__init__.py +7 -8
  169. endoreg_db/models/label/annotation/image_classification.py +10 -9
  170. endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
  171. endoreg_db/models/label/label.py +15 -15
  172. endoreg_db/models/label/label_set.py +19 -6
  173. endoreg_db/models/label/label_type.py +1 -1
  174. endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
  175. endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
  176. endoreg_db/models/label/video_segmentation_label.py +4 -0
  177. endoreg_db/models/label/video_segmentation_labelset.py +4 -3
  178. endoreg_db/models/media/frame/frame.py +22 -22
  179. endoreg_db/models/media/pdf/raw_pdf.py +249 -177
  180. endoreg_db/models/media/pdf/report_file.py +25 -29
  181. endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
  182. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
  183. endoreg_db/models/media/video/__init__.py +1 -0
  184. endoreg_db/models/media/video/create_from_file.py +48 -56
  185. endoreg_db/models/media/video/pipe_1.py +30 -33
  186. endoreg_db/models/media/video/pipe_2.py +8 -9
  187. endoreg_db/models/media/video/video_file.py +359 -204
  188. endoreg_db/models/media/video/video_file_ai.py +288 -74
  189. endoreg_db/models/media/video/video_file_anonymize.py +38 -38
  190. endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
  191. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
  192. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
  193. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
  194. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
  195. endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
  196. endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
  197. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
  198. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
  199. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
  200. endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
  201. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
  202. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
  203. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
  204. endoreg_db/models/media/video/video_file_io.py +109 -62
  205. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
  206. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
  207. endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
  208. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
  209. endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
  210. endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
  211. endoreg_db/models/media/video/video_file_segments.py +24 -17
  212. endoreg_db/models/media/video/video_metadata.py +19 -35
  213. endoreg_db/models/media/video/video_processing.py +96 -95
  214. endoreg_db/models/medical/contraindication/__init__.py +13 -3
  215. endoreg_db/models/medical/disease.py +22 -16
  216. endoreg_db/models/medical/event.py +31 -18
  217. endoreg_db/models/medical/examination/__init__.py +13 -6
  218. endoreg_db/models/medical/examination/examination.py +17 -18
  219. endoreg_db/models/medical/examination/examination_indication.py +26 -25
  220. endoreg_db/models/medical/examination/examination_time.py +16 -6
  221. endoreg_db/models/medical/examination/examination_time_type.py +9 -6
  222. endoreg_db/models/medical/examination/examination_type.py +3 -4
  223. endoreg_db/models/medical/finding/finding.py +38 -39
  224. endoreg_db/models/medical/finding/finding_classification.py +37 -48
  225. endoreg_db/models/medical/finding/finding_intervention.py +27 -22
  226. endoreg_db/models/medical/finding/finding_type.py +13 -12
  227. endoreg_db/models/medical/hardware/endoscope.py +20 -26
  228. endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
  229. endoreg_db/models/medical/laboratory/lab_value.py +62 -91
  230. endoreg_db/models/medical/medication/medication.py +22 -10
  231. endoreg_db/models/medical/medication/medication_indication.py +29 -3
  232. endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
  233. endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
  234. endoreg_db/models/medical/medication/medication_schedule.py +27 -16
  235. endoreg_db/models/medical/organ/__init__.py +15 -12
  236. endoreg_db/models/medical/patient/medication_examples.py +1 -5
  237. endoreg_db/models/medical/patient/patient_disease.py +20 -23
  238. endoreg_db/models/medical/patient/patient_event.py +19 -22
  239. endoreg_db/models/medical/patient/patient_examination.py +48 -54
  240. endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
  241. endoreg_db/models/medical/patient/patient_finding.py +122 -139
  242. endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
  243. endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
  244. endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
  245. endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
  246. endoreg_db/models/medical/patient/patient_medication.py +27 -38
  247. endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
  248. endoreg_db/models/medical/risk/risk.py +7 -6
  249. endoreg_db/models/medical/risk/risk_type.py +8 -5
  250. endoreg_db/models/metadata/model_meta.py +60 -29
  251. endoreg_db/models/metadata/model_meta_logic.py +139 -18
  252. endoreg_db/models/metadata/pdf_meta.py +19 -24
  253. endoreg_db/models/metadata/sensitive_meta.py +102 -85
  254. endoreg_db/models/metadata/sensitive_meta_logic.py +383 -43
  255. endoreg_db/models/metadata/video_meta.py +51 -31
  256. endoreg_db/models/metadata/video_prediction_logic.py +16 -23
  257. endoreg_db/models/metadata/video_prediction_meta.py +29 -33
  258. endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
  259. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
  260. endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
  261. endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
  262. endoreg_db/models/other/emission/emission_factor.py +18 -8
  263. endoreg_db/models/other/gender.py +10 -5
  264. endoreg_db/models/other/information_source.py +25 -25
  265. endoreg_db/models/other/material.py +9 -5
  266. endoreg_db/models/other/resource.py +6 -4
  267. endoreg_db/models/other/tag.py +10 -5
  268. endoreg_db/models/other/transport_route.py +13 -8
  269. endoreg_db/models/other/unit.py +10 -6
  270. endoreg_db/models/other/waste.py +6 -5
  271. endoreg_db/models/requirement/requirement.py +580 -272
  272. endoreg_db/models/requirement/requirement_error.py +85 -0
  273. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
  274. endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
  275. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
  276. endoreg_db/models/requirement/requirement_operator.py +36 -33
  277. endoreg_db/models/requirement/requirement_set.py +74 -57
  278. endoreg_db/models/state/__init__.py +4 -4
  279. endoreg_db/models/state/abstract.py +2 -2
  280. endoreg_db/models/state/anonymization.py +12 -0
  281. endoreg_db/models/state/audit_ledger.py +46 -47
  282. endoreg_db/models/state/label_video_segment.py +9 -0
  283. endoreg_db/models/state/raw_pdf.py +40 -46
  284. endoreg_db/models/state/sensitive_meta.py +6 -2
  285. endoreg_db/models/state/video.py +58 -53
  286. endoreg_db/models/upload_job.py +32 -55
  287. endoreg_db/models/utils.py +1 -2
  288. endoreg_db/root_urls.py +21 -2
  289. endoreg_db/serializers/__init__.py +26 -57
  290. endoreg_db/serializers/anonymization.py +18 -10
  291. endoreg_db/serializers/meta/report_meta.py +1 -1
  292. endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
  293. endoreg_db/serializers/misc/__init__.py +1 -1
  294. endoreg_db/serializers/misc/file_overview.py +33 -91
  295. endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
  296. endoreg_db/serializers/requirements/requirement_sets.py +92 -22
  297. endoreg_db/serializers/video/segmentation.py +2 -1
  298. endoreg_db/serializers/video/video_processing_history.py +20 -5
  299. endoreg_db/serializers/video_examination.py +198 -0
  300. endoreg_db/services/anonymization.py +75 -73
  301. endoreg_db/services/lookup_service.py +256 -73
  302. endoreg_db/services/lookup_store.py +174 -30
  303. endoreg_db/services/pdf_import.py +711 -310
  304. endoreg_db/services/storage_aware_video_processor.py +140 -114
  305. endoreg_db/services/video_import.py +266 -117
  306. endoreg_db/urls/__init__.py +27 -27
  307. endoreg_db/urls/label_video_segments.py +2 -0
  308. endoreg_db/urls/media.py +108 -66
  309. endoreg_db/urls/root_urls.py +29 -0
  310. endoreg_db/utils/__init__.py +15 -5
  311. endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
  312. endoreg_db/utils/case_generator/__init__.py +3 -0
  313. endoreg_db/utils/dataloader.py +88 -16
  314. endoreg_db/utils/defaults/set_default_center.py +32 -0
  315. endoreg_db/utils/names.py +22 -16
  316. endoreg_db/utils/permissions.py +2 -1
  317. endoreg_db/utils/pipelines/process_video_dir.py +1 -1
  318. endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
  319. endoreg_db/utils/setup_config.py +8 -5
  320. endoreg_db/utils/storage.py +115 -0
  321. endoreg_db/utils/validate_endo_roi.py +8 -2
  322. endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
  323. endoreg_db/views/__init__.py +5 -12
  324. endoreg_db/views/anonymization/media_management.py +198 -163
  325. endoreg_db/views/anonymization/overview.py +4 -1
  326. endoreg_db/views/anonymization/validate.py +174 -40
  327. endoreg_db/views/media/__init__.py +2 -0
  328. endoreg_db/views/media/pdf_media.py +131 -150
  329. endoreg_db/views/media/sensitive_metadata.py +46 -6
  330. endoreg_db/views/media/video_media.py +89 -82
  331. endoreg_db/views/media/video_segments.py +187 -260
  332. endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
  333. endoreg_db/views/patient/patient.py +5 -4
  334. endoreg_db/views/pdf/__init__.py +5 -8
  335. endoreg_db/views/pdf/pdf_stream.py +186 -0
  336. endoreg_db/views/pdf/pdf_stream_views.py +0 -127
  337. endoreg_db/views/pdf/reimport.py +86 -91
  338. endoreg_db/views/requirement/evaluate.py +188 -187
  339. endoreg_db/views/requirement/lookup.py +186 -288
  340. endoreg_db/views/requirement/requirement_utils.py +89 -0
  341. endoreg_db/views/video/__init__.py +0 -4
  342. endoreg_db/views/video/correction.py +2 -2
  343. endoreg_db/views/video/video_examination_viewset.py +202 -289
  344. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
  345. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +350 -255
  346. endoreg_db/models/administration/permissions/__init__.py +0 -44
  347. endoreg_db/models/media/video/refactor_plan.md +0 -0
  348. endoreg_db/models/media/video/video_file_frames.py +0 -0
  349. endoreg_db/models/metadata/frame_ocr_result.py +0 -0
  350. endoreg_db/models/rule/__init__.py +0 -13
  351. endoreg_db/models/rule/rule.py +0 -27
  352. endoreg_db/models/rule/rule_applicator.py +0 -224
  353. endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
  354. endoreg_db/models/rule/rule_type.py +0 -20
  355. endoreg_db/models/rule/ruleset.py +0 -17
  356. endoreg_db/serializers/video/video_metadata.py +0 -105
  357. endoreg_db/urls/report.py +0 -48
  358. endoreg_db/urls/video.py +0 -61
  359. endoreg_db/utils/case_generator/case_generator.py +0 -159
  360. endoreg_db/utils/case_generator/utils.py +0 -30
  361. endoreg_db/views/pdf/pdf_media.py +0 -239
  362. endoreg_db/views/report/__init__.py +0 -9
  363. endoreg_db/views/report/report_list.py +0 -112
  364. endoreg_db/views/report/report_with_secure_url.py +0 -28
  365. endoreg_db/views/report/start_examination.py +0 -7
  366. endoreg_db/views/video/video_media.py +0 -158
  367. endoreg_db/views.py +0 -0
  368. /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
  369. /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
  370. /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
  371. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
  372. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,14 +9,18 @@ This is separate from the existing pdf.PDFMediaView which handles legacy workflo
9
9
 
10
10
  import logging
11
11
  import os
12
- from django.http import Http404, FileResponse
12
+ from pathlib import Path
13
+
14
+ from django.db.models import Q
15
+ from django.http import FileResponse, Http404
16
+ from django.views.decorators.clickjacking import xframe_options_exempt
13
17
  from rest_framework import status
14
18
  from rest_framework.response import Response
15
19
  from rest_framework.views import APIView
16
- from django.db.models import Q
17
20
 
18
21
  from endoreg_db.models import RawPdfFile
19
22
  from endoreg_db.utils.permissions import EnvironmentAwarePermission
23
+ from endoreg_db.utils.storage import file_exists
20
24
 
21
25
  logger = logging.getLogger(__name__)
22
26
 
@@ -24,25 +28,25 @@ logger = logging.getLogger(__name__)
24
28
  class PdfMediaView(APIView):
25
29
  """
26
30
  PDF Media Management API for CRUD operations on PDF files.
27
-
31
+
28
32
  Endpoints:
29
33
  - GET /api/media/pdfs/ - List all PDFs with filtering
30
34
  - GET /api/media/pdfs/{id}/ - Get PDF details
31
35
  - GET /api/media/pdfs/{id}/stream/ - Stream PDF file (same as detail for PDFs)
32
36
  - PATCH /api/media/pdfs/{id}/ - Update PDF metadata (future)
33
37
  - DELETE /api/media/pdfs/{id}/ - Delete PDF (future)
34
-
38
+
35
39
  Query Parameters:
36
40
  - status: Filter by processing status (not_started, done, validated)
37
41
  - search: Search in filename
38
42
  - limit: Limit results (default: 50)
39
43
  - offset: Pagination offset
40
-
44
+
41
45
  Examples:
42
46
  - GET /api/media/pdfs/?status=done&search=exam
43
47
  - GET /api/media/pdfs/123/
44
48
  - GET /api/media/pdfs/123/stream/
45
-
49
+
46
50
  Phase 1.2 Implementation:
47
51
  - List and detail views implemented
48
52
  - PDF streaming functionality
@@ -50,25 +54,26 @@ class PdfMediaView(APIView):
50
54
  - Pagination support
51
55
  - Error handling with proper HTTP status codes
52
56
  """
57
+
53
58
  permission_classes = [EnvironmentAwarePermission]
54
59
 
55
60
  def get(self, request, pk=None):
56
61
  """
57
62
  Handle GET requests for PDF listing, detail retrieval, or streaming.
58
-
63
+
59
64
  Args:
60
65
  request: HTTP request object
61
66
  pk: Optional PDF ID for detail view or streaming
62
-
67
+
63
68
  Returns:
64
69
  Response or FileResponse: JSON response with PDF data or PDF file stream
65
-
70
+
66
71
  Raises:
67
72
  Http404: If specific PDF not found
68
73
  """
69
74
  if pk is not None:
70
75
  # Check if this is a streaming request
71
- if request.path.endswith('/stream/'):
76
+ if request.path.endswith("/stream/"):
72
77
  return self._stream_pdf(pk)
73
78
  else:
74
79
  # Detail view
@@ -80,13 +85,13 @@ class PdfMediaView(APIView):
80
85
  def _get_pdf_detail(self, pk):
81
86
  """
82
87
  Get detailed information for a specific PDF.
83
-
88
+
84
89
  Args:
85
90
  pk: PDF primary key
86
-
91
+
87
92
  Returns:
88
93
  Response: JSON response with PDF details
89
-
94
+
90
95
  Raises:
91
96
  Http404: If PDF not found
92
97
  """
@@ -96,54 +101,54 @@ class PdfMediaView(APIView):
96
101
  pdf_id_int = int(pk)
97
102
  except (ValueError, TypeError):
98
103
  raise Http404("Invalid PDF ID format")
99
-
104
+
100
105
  # Fetch PDF with related data
101
- pdf = RawPdfFile.objects.select_related('sensitive_meta').get(pk=pdf_id_int)
102
-
106
+ pdf = RawPdfFile.objects.select_related("sensitive_meta").get(pk=pdf_id_int)
107
+
103
108
  # Build PDF details
104
109
  pdf_data = {
105
- "id": pdf.id,
106
- "filename": getattr(pdf.file, 'name', 'Unknown'),
107
- "file_size": getattr(pdf.file, 'size', 0),
110
+ "id": pdf.pk,
111
+ "filename": getattr(pdf.file, "name", "Unknown"),
112
+ "file_size": getattr(pdf.file, "size", 0),
108
113
  "pdf_hash": pdf.pdf_hash,
109
- "uploaded_at": pdf.uploaded_at.isoformat() if hasattr(pdf, 'uploaded_at') else None,
114
+ "uploaded_at": pdf.date_created.isoformat() if getattr(pdf, "date_created", None) else None,
110
115
  "anonymized_text": pdf.anonymized_text,
111
116
  "has_anonymized_text": bool(pdf.anonymized_text and pdf.anonymized_text.strip()),
112
- "is_validated": getattr(pdf.sensitive_meta, 'is_verified', False) if pdf.sensitive_meta else False,
113
- "stream_url": self.request.build_absolute_uri(f"/api/media/pdfs/{pdf.id}/stream/"),
117
+ "is_validated": getattr(pdf.sensitive_meta, "is_verified", False) if pdf.sensitive_meta else False,
118
+ "stream_url": self.request.build_absolute_uri(f"/api/media/pdfs/{pdf.pk}/stream/"),
114
119
  }
115
-
120
+
116
121
  # Add patient metadata if available
117
122
  if pdf.sensitive_meta:
118
- pdf_data.update({
119
- "patient_first_name": pdf.sensitive_meta.patient_first_name,
120
- "patient_last_name": pdf.sensitive_meta.patient_last_name,
121
- "patient_dob": pdf.sensitive_meta.patient_dob.strftime("%d.%m.%Y") if pdf.sensitive_meta.patient_dob else None,
122
- "examination_date": pdf.sensitive_meta.examination_date.strftime("%d.%m.%Y") if pdf.sensitive_meta.examination_date else None,
123
- })
124
-
123
+ pdf_data.update(
124
+ {
125
+ "patient_first_name": pdf.sensitive_meta.patient_first_name,
126
+ "patient_last_name": pdf.sensitive_meta.patient_last_name,
127
+ "patient_dob": pdf.sensitive_meta.patient_dob.strftime("%d.%m.%Y") if pdf.sensitive_meta.patient_dob else None,
128
+ "examination_date": pdf.sensitive_meta.examination_date.strftime("%d.%m.%Y") if pdf.sensitive_meta.examination_date else None,
129
+ }
130
+ )
131
+
125
132
  return Response(pdf_data)
126
-
133
+
127
134
  except RawPdfFile.DoesNotExist:
128
135
  raise Http404(f"PDF with ID {pk} not found")
129
-
136
+
130
137
  except Exception as e:
131
138
  logger.error(f"Unexpected error in PDF detail view for ID {pk}: {str(e)}")
132
- return Response(
133
- {"error": "Failed to retrieve PDF details"},
134
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
135
- )
139
+ return Response({"error": "Failed to retrieve PDF details"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
136
140
 
141
+ @xframe_options_exempt
137
142
  def _stream_pdf(self, pk):
138
143
  """
139
144
  Stream PDF file content for viewing/download.
140
-
145
+
141
146
  Args:
142
147
  pk: PDF primary key
143
-
148
+
144
149
  Returns:
145
150
  FileResponse: PDF file stream
146
-
151
+
147
152
  Raises:
148
153
  Http404: If PDF not found or file cannot be accessed
149
154
  """
@@ -153,45 +158,41 @@ class PdfMediaView(APIView):
153
158
  pdf_id_int = int(pk)
154
159
  except (ValueError, TypeError):
155
160
  raise Http404("Invalid PDF ID format")
156
-
161
+
157
162
  # Fetch PDF
158
163
  pdf = RawPdfFile.objects.get(pk=pdf_id_int)
159
-
160
- # Check if file exists
161
- if not pdf.file or not pdf.file.name:
164
+
165
+ file_field = pdf.file
166
+
167
+ if not file_field or not file_field.name:
162
168
  raise Http404("PDF file not found")
163
-
169
+ if not file_exists(file_field):
170
+ raise Http404("PDF file does not exist in storage")
171
+
164
172
  try:
165
- # Open file for streaming
166
- file_path = pdf.file.path
167
- if not os.path.exists(file_path):
168
- raise Http404("PDF file does not exist on disk")
169
-
170
- # Create file response
171
- response = FileResponse(
172
- open(file_path, 'rb'),
173
- content_type='application/pdf',
174
- as_attachment=False # View in browser, not download
175
- )
176
-
177
- # Set filename for browser
178
- filename = os.path.basename(pdf.file.name)
179
- response['Content-Disposition'] = f'inline; filename="{filename}"'
180
-
181
- # CORS headers for frontend access
182
- frontend_origin = os.environ.get('FRONTEND_ORIGIN', 'http://localhost:8000')
183
- response['Access-Control-Allow-Origin'] = frontend_origin
184
- response['Access-Control-Allow-Credentials'] = 'true'
185
-
186
- return response
187
-
188
- except (OSError, IOError) as e:
189
- logger.error(f"File access error for PDF {pk}: {str(e)}")
173
+ file_field.open("rb")
174
+ except Exception as exc: # pragma: no cover - backend-specific failure
175
+ logger.error("File access error for PDF %s: %s", pk, exc)
190
176
  raise Http404("PDF file cannot be accessed")
191
-
177
+
178
+ response = FileResponse(
179
+ file_field,
180
+ content_type="application/pdf",
181
+ as_attachment=False,
182
+ )
183
+
184
+ filename = Path(file_field.name).name
185
+ response["Content-Disposition"] = f'inline; filename="{filename}"'
186
+
187
+ frontend_origin = os.environ.get("FRONTEND_ORIGIN", "http://localhost:8000")
188
+ response["Access-Control-Allow-Origin"] = frontend_origin
189
+ response["Access-Control-Allow-Credentials"] = "true"
190
+
191
+ return response
192
+
192
193
  except RawPdfFile.DoesNotExist:
193
194
  raise Http404(f"PDF with ID {pk} not found")
194
-
195
+
195
196
  except Exception as e:
196
197
  logger.error(f"Unexpected error in PDF streaming for ID {pk}: {str(e)}")
197
198
  raise Http404("PDF file cannot be streamed")
@@ -199,53 +200,51 @@ class PdfMediaView(APIView):
199
200
  def _list_pdfs(self, request):
200
201
  """
201
202
  List PDFs with filtering, search, and pagination.
202
-
203
+
203
204
  Args:
204
205
  request: HTTP request with query parameters
205
-
206
+
206
207
  Returns:
207
208
  Response: JSON response with paginated PDF list
208
209
  """
209
210
  try:
210
211
  # Start with all PDFs
211
- queryset = RawPdfFile.objects.select_related('sensitive_meta').all()
212
-
212
+ queryset = RawPdfFile.objects.select_related("sensitive_meta").all()
213
+
213
214
  # Apply filters
214
215
  queryset = self._apply_filters(queryset, request.query_params)
215
-
216
+
216
217
  # Apply search
217
- search = request.query_params.get('search', '').strip()
218
+ search = request.query_params.get("search", "").strip()
218
219
  if search:
219
- queryset = queryset.filter(
220
- Q(file__icontains=search)
221
- )
222
-
220
+ queryset = queryset.filter(Q(file__icontains=search))
221
+
223
222
  # Order by upload date (newest first) or id if no upload date
224
- if hasattr(queryset.model, 'uploaded_at'):
225
- queryset = queryset.order_by('-uploaded_at')
223
+ if hasattr(queryset.model, "date_created"):
224
+ queryset = queryset.order_by("-date_created")
226
225
  else:
227
- queryset = queryset.order_by('-id')
228
-
226
+ queryset = queryset.order_by("-pk")
227
+
229
228
  # Apply pagination
230
- limit = min(int(request.query_params.get('limit', 50)), 100)
231
- offset = int(request.query_params.get('offset', 0))
232
-
229
+ limit = min(int(request.query_params.get("limit", 50)), 100)
230
+ offset = int(request.query_params.get("offset", 0))
231
+
233
232
  total_count = queryset.count()
234
- pdfs = queryset[offset:offset + limit]
235
-
233
+ pdfs = queryset[offset : offset + limit]
234
+
236
235
  # Serialize PDFs manually (no dedicated serializer yet)
237
236
  results = []
238
237
  for pdf in pdfs:
239
238
  pdf_item = {
240
- "id": pdf.id,
241
- "filename": getattr(pdf.file, 'name', 'Unknown'),
239
+ "id": pdf.pk,
240
+ "filename": getattr(pdf.file, "name", "Unknown"),
242
241
  "file_size": self._safe_get_file_size(pdf.file),
243
242
  "pdf_hash": pdf.pdf_hash,
244
243
  "has_anonymized_text": bool(pdf.anonymized_text and pdf.anonymized_text.strip()),
245
- "is_validated": getattr(pdf.sensitive_meta, 'is_verified', False) if pdf.sensitive_meta else False,
246
- "stream_url": request.build_absolute_uri(f"/api/media/pdfs/{pdf.id}/stream/"),
244
+ "is_validated": getattr(pdf.sensitive_meta, "is_verified", False) if pdf.sensitive_meta else False,
245
+ "stream_url": request.build_absolute_uri(f"/api/media/pdfs/{pdf.pk}/stream/"),
247
246
  }
248
-
247
+
249
248
  # Determine status based on anonymization and validation
250
249
  if not pdf.anonymized_text or not pdf.anonymized_text.strip():
251
250
  pdf_item["status"] = "not_started"
@@ -253,42 +252,38 @@ class PdfMediaView(APIView):
253
252
  pdf_item["status"] = "validated"
254
253
  else:
255
254
  pdf_item["status"] = "done"
256
-
255
+
257
256
  results.append(pdf_item)
258
-
259
- return Response({
260
- "count": total_count,
261
- "next": self._get_next_url(request, offset, limit, total_count),
262
- "previous": self._get_previous_url(request, offset, limit),
263
- "results": results
264
- })
265
-
266
- except ValueError as e:
257
+
267
258
  return Response(
268
- {"error": f"Invalid query parameter: {str(e)}"},
269
- status=status.HTTP_400_BAD_REQUEST
259
+ {
260
+ "count": total_count,
261
+ "next": self._get_next_url(request, offset, limit, total_count),
262
+ "previous": self._get_previous_url(request, offset, limit),
263
+ "results": results,
264
+ }
270
265
  )
271
-
266
+
267
+ except ValueError as e:
268
+ return Response({"error": f"Invalid query parameter: {str(e)}"}, status=status.HTTP_400_BAD_REQUEST)
269
+
272
270
  except Exception as e:
273
271
  logger.error(f"Unexpected error in PDF list view: {str(e)}")
274
- return Response(
275
- {"error": "Failed to retrieve PDF list"},
276
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
277
- )
272
+ return Response({"error": "Failed to retrieve PDF list"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
278
273
 
279
274
  def _safe_get_file_size(self, file_field):
280
275
  """
281
276
  Safely get file size without causing errors if file doesn't exist.
282
-
277
+
283
278
  Args:
284
279
  file_field: Django FileField
285
-
280
+
286
281
  Returns:
287
282
  int: File size in bytes, or 0 if file doesn't exist
288
283
  """
289
284
  if not file_field or not file_field.name:
290
285
  return 0
291
-
286
+
292
287
  try:
293
288
  return file_field.size
294
289
  except (OSError, IOError, ValueError):
@@ -298,44 +293,36 @@ class PdfMediaView(APIView):
298
293
  def _apply_filters(self, queryset, query_params):
299
294
  """
300
295
  Apply status and other filters to PDF queryset.
301
-
296
+
302
297
  Args:
303
298
  queryset: Base queryset to filter
304
299
  query_params: Request query parameters
305
-
300
+
306
301
  Returns:
307
302
  QuerySet: Filtered queryset
308
303
  """
309
- status_filter = query_params.get('status', '').strip().lower()
310
-
304
+ status_filter = query_params.get("status", "").strip().lower()
305
+
311
306
  if status_filter:
312
- if status_filter == 'not_started':
307
+ if status_filter == "not_started":
313
308
  # PDFs without anonymized text
314
- queryset = queryset.filter(
315
- Q(anonymized_text__isnull=True) | Q(anonymized_text__exact='')
316
- )
317
- elif status_filter == 'done':
309
+ queryset = queryset.filter(Q(anonymized_text__isnull=True) | Q(anonymized_text__exact=""))
310
+ elif status_filter == "done":
318
311
  # PDFs with anonymized text but not validated
319
312
  queryset = queryset.filter(
320
- ~Q(anonymized_text__isnull=True),
321
- ~Q(anonymized_text__exact=''),
322
- Q(sensitive_meta__is_verified=False) | Q(sensitive_meta__isnull=True)
313
+ ~Q(anonymized_text__isnull=True), ~Q(anonymized_text__exact=""), Q(sensitive_meta__is_verified=False) | Q(sensitive_meta__isnull=True)
323
314
  )
324
- elif status_filter == 'validated':
315
+ elif status_filter == "validated":
325
316
  # PDFs with anonymized text and validated
326
- queryset = queryset.filter(
327
- ~Q(anonymized_text__isnull=True),
328
- ~Q(anonymized_text__exact=''),
329
- sensitive_meta__is_verified=True
330
- )
331
-
317
+ queryset = queryset.filter(~Q(anonymized_text__isnull=True), ~Q(anonymized_text__exact=""), sensitive_meta__is_verified=True)
318
+
332
319
  return queryset
333
320
 
334
321
  def _get_next_url(self, request, offset, limit, total_count):
335
322
  """Generate next page URL for pagination."""
336
323
  if offset + limit >= total_count:
337
324
  return None
338
-
325
+
339
326
  next_offset = offset + limit
340
327
  return self._build_paginated_url(request, next_offset, limit)
341
328
 
@@ -343,16 +330,16 @@ class PdfMediaView(APIView):
343
330
  """Generate previous page URL for pagination."""
344
331
  if offset <= 0:
345
332
  return None
346
-
333
+
347
334
  prev_offset = max(0, offset - limit)
348
335
  return self._build_paginated_url(request, prev_offset, limit)
349
336
 
350
337
  def _build_paginated_url(self, request, offset, limit):
351
338
  """Build URL with pagination parameters."""
352
339
  params = request.query_params.copy()
353
- params['offset'] = offset
354
- params['limit'] = limit
355
-
340
+ params["offset"] = offset
341
+ params["limit"] = limit
342
+
356
343
  base_url = request.build_absolute_uri(request.path)
357
344
  if params:
358
345
  return f"{base_url}?{params.urlencode()}"
@@ -362,25 +349,19 @@ class PdfMediaView(APIView):
362
349
  def patch(self, request, pk):
363
350
  """
364
351
  Update PDF metadata (Phase 1.2+ future enhancement).
365
-
352
+
366
353
  Currently returns 501 Not Implemented.
367
354
  """
368
- return Response(
369
- {"error": "PDF metadata updates not yet implemented"},
370
- status=status.HTTP_501_NOT_IMPLEMENTED
371
- )
355
+ return Response({"error": "PDF metadata updates not yet implemented"}, status=status.HTTP_501_NOT_IMPLEMENTED)
372
356
 
373
357
  def delete(self, request, pk):
374
358
  """
375
359
  Delete PDF file (Phase 1.2+ future enhancement).
376
-
360
+
377
361
  Currently returns 501 Not Implemented.
378
362
  Use /api/media-management/force-remove/{id}/ instead.
379
363
  """
380
364
  return Response(
381
- {
382
- "error": "PDF deletion not yet implemented",
383
- "alternative": f"Use DELETE /api/media-management/force-remove/{pk}/ instead"
384
- },
385
- status=status.HTTP_501_NOT_IMPLEMENTED
365
+ {"error": "PDF deletion not yet implemented", "alternative": f"Use DELETE /api/media-management/force-remove/{pk}/ instead"},
366
+ status=status.HTTP_501_NOT_IMPLEMENTED,
386
367
  )
@@ -1,4 +1,6 @@
1
1
  # Modern Media Framework: Sensitive Metadata Management
2
+ import string
3
+ from numpy import number
2
4
  from rest_framework.decorators import api_view, permission_classes
3
5
  from rest_framework.response import Response
4
6
  from rest_framework import status
@@ -14,6 +16,46 @@ from endoreg_db.serializers.meta import (
14
16
 
15
17
  # === VIDEO SENSITIVE METADATA ===
16
18
 
19
+ @api_view(['GET'])
20
+ @permission_classes([EnvironmentAwarePermission])
21
+ def get_sensitive_metadata_pk(request, pk: number, mediaType: str) -> Response | None:
22
+ """
23
+ A route to get the sensitive meta pk for a media type quickly.
24
+
25
+ GET api/media/sensitive-media-id/<pk>/<str:mediaType>
26
+
27
+ Args:
28
+ request (_type_): _description_
29
+ id (_type_): _description_
30
+
31
+ Returns:
32
+ Response | None: _description_
33
+ """
34
+
35
+ if mediaType == 'video':
36
+ video = get_object_or_404(VideoFile, pk=pk)
37
+ if not video.sensitive_meta:
38
+ return Response(
39
+ {"error": f"No sensitive metadata found for video {pk}"},
40
+ status=status.HTTP_404_NOT_FOUND
41
+ )
42
+ sm_id = video.sensitive_meta.pk
43
+ return Response({
44
+ "sm": sm_id
45
+ })
46
+ if mediaType == 'pdf':
47
+ pdf = get_object_or_404(RawPdfFile, pk=pk)
48
+ if not pdf.sensitive_meta:
49
+ return Response(
50
+ {"error": f"No sensitive metadata found for PDF {pk}"},
51
+ status=status.HTTP_404_NOT_FOUND
52
+ )
53
+ sm_id = pdf.sensitive_meta.pk
54
+ return Response({
55
+ "sm": sm_id
56
+ })
57
+
58
+
17
59
  @api_view(['GET', 'PATCH'])
18
60
  @permission_classes([EnvironmentAwarePermission])
19
61
  def video_sensitive_metadata(request, pk):
@@ -24,16 +66,15 @@ def video_sensitive_metadata(request, pk):
24
66
  Get or update sensitive metadata for a video.
25
67
  Video-scoped: Uses video ID to locate related sensitive metadata.
26
68
  """
27
- video = get_object_or_404(VideoFile, pk=pk)
69
+ sensitive_meta = get_object_or_404(SensitiveMeta, pk=pk)
28
70
 
29
71
  # Get related sensitive metadata
30
- if not video.sensitive_meta:
72
+ if not sensitive_meta:
31
73
  return Response(
32
74
  {"error": f"No sensitive metadata found for video {pk}"},
33
75
  status=status.HTTP_404_NOT_FOUND
34
76
  )
35
77
 
36
- sensitive_meta = video.sensitive_meta
37
78
 
38
79
  if request.method == 'GET':
39
80
  serializer = SensitiveMetaDetailSerializer(sensitive_meta)
@@ -123,16 +164,15 @@ def pdf_sensitive_metadata(request, pk):
123
164
  Get or update sensitive metadata for a PDF.
124
165
  PDF-scoped: Uses PDF ID to locate related sensitive metadata.
125
166
  """
126
- pdf = get_object_or_404(RawPdfFile, pk=pk)
167
+ sensitive_meta = get_object_or_404(SensitiveMeta, pk=pk)
127
168
 
128
169
  # Get related sensitive metadata
129
- if not pdf.sensitive_meta:
170
+ if not sensitive_meta:
130
171
  return Response(
131
172
  {"error": f"No sensitive metadata found for PDF {pk}"},
132
173
  status=status.HTTP_404_NOT_FOUND
133
174
  )
134
175
 
135
- sensitive_meta = pdf.sensitive_meta
136
176
 
137
177
  if request.method == 'GET':
138
178
  serializer = SensitiveMetaDetailSerializer(sensitive_meta)