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