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.
- 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_ai_model_data.py +2 -1
- 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 +14 -10
- 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 +249 -177
- 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_1.py +30 -33
- endoreg_db/models/media/video/pipe_2.py +8 -9
- endoreg_db/models/media/video/video_file.py +359 -204
- 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 +139 -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 +383 -43
- 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 +26 -57
- 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/__init__.py +1 -1
- endoreg_db/serializers/misc/file_overview.py +33 -91
- endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
- 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/serializers/video_examination.py +198 -0
- endoreg_db/services/anonymization.py +75 -73
- endoreg_db/services/lookup_service.py +256 -73
- endoreg_db/services/lookup_store.py +174 -30
- endoreg_db/services/pdf_import.py +711 -310
- endoreg_db/services/storage_aware_video_processor.py +140 -114
- endoreg_db/services/video_import.py +266 -117
- endoreg_db/urls/__init__.py +27 -27
- endoreg_db/urls/label_video_segments.py +2 -0
- endoreg_db/urls/media.py +108 -66
- 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 +5 -12
- 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 -150
- 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 +187 -260
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
- endoreg_db/views/patient/patient.py +5 -4
- endoreg_db/views/pdf/__init__.py +5 -8
- endoreg_db/views/pdf/pdf_stream.py +186 -0
- endoreg_db/views/pdf/pdf_stream_views.py +0 -127
- endoreg_db/views/pdf/reimport.py +86 -91
- endoreg_db/views/requirement/evaluate.py +188 -187
- endoreg_db/views/requirement/lookup.py +186 -288
- endoreg_db/views/requirement/requirement_utils.py +89 -0
- endoreg_db/views/video/__init__.py +0 -4
- endoreg_db/views/video/correction.py +2 -2
- endoreg_db/views/video/video_examination_viewset.py +202 -289
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +350 -255
- endoreg_db/models/administration/permissions/__init__.py +0 -44
- endoreg_db/models/media/video/refactor_plan.md +0 -0
- 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/pdf/pdf_media.py +0 -239
- 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/video/video_media.py +0 -158
- 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-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -20,69 +20,6 @@ class SensitiveMetaDetailView(APIView):
|
|
|
20
20
|
PATCH: Updates SensitiveMeta fields including verification state
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
permission_classes = DEBUG_PERMISSIONS # Changed from IsAuthenticated for development
|
|
24
|
-
|
|
25
|
-
def get(self, request, sensitive_meta_id=None):
|
|
26
|
-
"""
|
|
27
|
-
Retrieve SensitiveMeta details for display and annotation.
|
|
28
|
-
|
|
29
|
-
Supports both URL parameter and query parameter access patterns:
|
|
30
|
-
- /api/pdf/sensitivemeta/123/ (URL parameter)
|
|
31
|
-
- /api/pdf/sensitivemeta/?id=123 (query parameter - for backward compatibility)
|
|
32
|
-
- /api/pdf/sensitivemeta/ (list all - returns empty list instead of 400)
|
|
33
|
-
|
|
34
|
-
Returns detailed information suitable for user verification.
|
|
35
|
-
"""
|
|
36
|
-
# Handle both URL parameter and query parameter patterns
|
|
37
|
-
if not sensitive_meta_id:
|
|
38
|
-
sensitive_meta_id = request.query_params.get('id')
|
|
39
|
-
|
|
40
|
-
# If no ID provided, return empty list instead of error
|
|
41
|
-
if not sensitive_meta_id:
|
|
42
|
-
return Response([], status=status.HTTP_200_OK)
|
|
43
|
-
|
|
44
|
-
try:
|
|
45
|
-
# Convert to int if it's a string
|
|
46
|
-
sensitive_meta_id = int(sensitive_meta_id)
|
|
47
|
-
|
|
48
|
-
# Get the SensitiveMeta instance with related data
|
|
49
|
-
sensitive_meta = SensitiveMeta.objects.select_related(
|
|
50
|
-
'center',
|
|
51
|
-
'patient_gender',
|
|
52
|
-
'pseudo_patient',
|
|
53
|
-
'pseudo_examination'
|
|
54
|
-
).prefetch_related(
|
|
55
|
-
'examiners',
|
|
56
|
-
'state'
|
|
57
|
-
).get(id=sensitive_meta_id)
|
|
58
|
-
|
|
59
|
-
# Serialize for display
|
|
60
|
-
serializer = SensitiveMetaDetailSerializer(sensitive_meta)
|
|
61
|
-
|
|
62
|
-
# Return direct data to match anonymization store expectations
|
|
63
|
-
# Instead of wrapping in "sensitive_meta" key, return data directly
|
|
64
|
-
response_data = serializer.data
|
|
65
|
-
|
|
66
|
-
logger.info(f"Retrieved SensitiveMeta {sensitive_meta_id} for user {request.user}")
|
|
67
|
-
return Response(response_data, status=status.HTTP_200_OK)
|
|
68
|
-
|
|
69
|
-
except ValueError:
|
|
70
|
-
return Response(
|
|
71
|
-
{"error": "Invalid sensitive_meta_id format. Must be an integer."},
|
|
72
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
73
|
-
)
|
|
74
|
-
except SensitiveMeta.DoesNotExist:
|
|
75
|
-
return Response(
|
|
76
|
-
{"error": f"SensitiveMeta with ID {sensitive_meta_id} not found"},
|
|
77
|
-
status=status.HTTP_404_NOT_FOUND
|
|
78
|
-
)
|
|
79
|
-
except Exception as e:
|
|
80
|
-
logger.error(f"Error retrieving SensitiveMeta {sensitive_meta_id}: {e}")
|
|
81
|
-
return Response(
|
|
82
|
-
{"error": "Internal server error occurred"},
|
|
83
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
84
|
-
)
|
|
85
|
-
|
|
86
23
|
@transaction.atomic
|
|
87
24
|
def patch(self, request, sensitive_meta_id=None):
|
|
88
25
|
"""
|
|
@@ -6,11 +6,11 @@ from rest_framework import viewsets, status, serializers
|
|
|
6
6
|
from rest_framework.response import Response
|
|
7
7
|
from rest_framework.decorators import action
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
from endoreg_db.authz.permissions import PolicyPermission
|
|
10
10
|
from endoreg_db.models import Patient
|
|
11
11
|
from endoreg_db.serializers.patient import PatientSerializer
|
|
12
12
|
from endoreg_db.models.medical.patient.patient_examination import PatientExamination
|
|
13
|
-
|
|
13
|
+
from rest_framework.permissions import IsAuthenticated
|
|
14
14
|
@staff_member_required # Ensures only staff members can access the page
|
|
15
15
|
def start_examination(request):
|
|
16
16
|
return render(request, 'admin/start_examination.html') # Loads the simple HTML page
|
|
@@ -21,8 +21,9 @@ class PatientViewSet(viewsets.ModelViewSet):
|
|
|
21
21
|
"""API endpoint for managing patients."""
|
|
22
22
|
queryset = Patient.objects.all()
|
|
23
23
|
serializer_class = PatientSerializer
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
permission_classes = [PolicyPermission]
|
|
25
|
+
#permission_classes = [PolicyPermission]
|
|
26
|
+
|
|
26
27
|
def perform_create(self, serializer):
|
|
27
28
|
"""Erweiterte Validierung beim Erstellen eines Patienten"""
|
|
28
29
|
try:
|
endoreg_db/views/pdf/__init__.py
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
from .
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
ClosingFileWrapper
|
|
5
|
-
)
|
|
1
|
+
from .reimport import PdfReimportView
|
|
2
|
+
from .pdf_stream import PdfStreamView
|
|
3
|
+
|
|
6
4
|
|
|
7
5
|
__all__ = [
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"ClosingFileWrapper",
|
|
6
|
+
"PdfReimportView",
|
|
7
|
+
"PdfStreamView",
|
|
11
8
|
]
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from django.http import FileResponse, Http404, StreamingHttpResponse
|
|
6
|
+
from django.views.decorators.clickjacking import xframe_options_exempt, xframe_options_sameorigin
|
|
7
|
+
from rest_framework.views import APIView
|
|
8
|
+
|
|
9
|
+
from endoreg_db.models import RawPdfFile
|
|
10
|
+
|
|
11
|
+
from ...utils.permissions import EnvironmentAwarePermission
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
_RANGE_RE = re.compile(r"bytes=(\d+)-(\d*)")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ClosingFileWrapper:
|
|
18
|
+
"""Custom file wrapper that ensures file is closed after streaming"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, file_handle, blksize=8192):
|
|
21
|
+
self.file_handle = file_handle
|
|
22
|
+
self.blksize = blksize
|
|
23
|
+
|
|
24
|
+
def __iter__(self):
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
def __next__(self):
|
|
28
|
+
data = self.file_handle.read(self.blksize)
|
|
29
|
+
if not data:
|
|
30
|
+
self.file_handle.close()
|
|
31
|
+
raise StopIteration
|
|
32
|
+
return data
|
|
33
|
+
|
|
34
|
+
def close(self):
|
|
35
|
+
if hasattr(self.file_handle, "close"):
|
|
36
|
+
self.file_handle.close()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PdfStreamView(APIView):
|
|
40
|
+
"""
|
|
41
|
+
Streams a PDF file with correct HTTP range support and proper file handle management.
|
|
42
|
+
|
|
43
|
+
Supports streaming both raw (original) and processed PDF files.
|
|
44
|
+
|
|
45
|
+
Query Parameters:
|
|
46
|
+
type: 'raw' (default) or 'processed' - Selects which PDF file to stream
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
GET /api/media/pdf/1/?type=raw - Stream original raw PDF
|
|
50
|
+
GET /api/media/pdf/1/?type=processed - Stream processed PDF
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
permission_classes = [EnvironmentAwarePermission]
|
|
54
|
+
@xframe_options_exempt
|
|
55
|
+
def get(self, request, pk: int, *args, **kwargs):
|
|
56
|
+
file_type = "raw" # Initialize for error logging
|
|
57
|
+
try:
|
|
58
|
+
pdf_obj = RawPdfFile.objects.filter(pk=pk).first()
|
|
59
|
+
if not pdf_obj:
|
|
60
|
+
logger.warning(f"PDF not found: ID {pk}")
|
|
61
|
+
raise Http404("PDF not found")
|
|
62
|
+
|
|
63
|
+
# Parse query parameters to determine which file to stream
|
|
64
|
+
file_type = request.query_params.get("type", "raw").lower()
|
|
65
|
+
if file_type not in ["raw", "processed"]:
|
|
66
|
+
logger.warning(f"Invalid file_type '{file_type}', defaulting to 'raw'")
|
|
67
|
+
file_type = "raw"
|
|
68
|
+
|
|
69
|
+
# Determine which file field to use
|
|
70
|
+
if file_type == "raw":
|
|
71
|
+
file_field = pdf_obj.file
|
|
72
|
+
if not file_field:
|
|
73
|
+
logger.warning(f"No raw PDF file available for PDF ID {pk}")
|
|
74
|
+
raise Http404("Raw PDF file not available")
|
|
75
|
+
else: # anonymized
|
|
76
|
+
file_field = pdf_obj.anonymized_file
|
|
77
|
+
if not file_field:
|
|
78
|
+
logger.warning(
|
|
79
|
+
f"No processed PDF file available for PDF ID {pk}"
|
|
80
|
+
)
|
|
81
|
+
raise Http404("Processed PDF file not available")
|
|
82
|
+
|
|
83
|
+
# Check if file exists on filesystem
|
|
84
|
+
try:
|
|
85
|
+
file_path = file_field.path
|
|
86
|
+
if not os.path.exists(file_path):
|
|
87
|
+
logger.error(f"PDF file does not exist on filesystem: {file_path}")
|
|
88
|
+
raise Http404(
|
|
89
|
+
f"{file_type.capitalize()} PDF file not found on filesystem"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
file_size = os.path.getsize(file_path)
|
|
93
|
+
except (OSError, IOError, AttributeError) as e:
|
|
94
|
+
logger.error(f"Error accessing {file_type} PDF file {pk}: {e}")
|
|
95
|
+
raise Http404(f"{file_type.capitalize()} PDF file not accessible")
|
|
96
|
+
|
|
97
|
+
# Generate safe filename
|
|
98
|
+
base_filename = (
|
|
99
|
+
os.path.basename(file_field.name)
|
|
100
|
+
if file_field.name
|
|
101
|
+
else f"document_{pk}.pdf"
|
|
102
|
+
)
|
|
103
|
+
if not base_filename.endswith(".pdf"):
|
|
104
|
+
base_filename += ".pdf"
|
|
105
|
+
|
|
106
|
+
# Add type indicator to filename for clarity
|
|
107
|
+
if file_type == "processed":
|
|
108
|
+
name_parts = base_filename.rsplit(".", 1)
|
|
109
|
+
safe_filename = f"{name_parts[0]}_processed.{name_parts[1]}"
|
|
110
|
+
else:
|
|
111
|
+
safe_filename = base_filename
|
|
112
|
+
|
|
113
|
+
# Handle Range requests
|
|
114
|
+
range_header = request.headers.get("Range")
|
|
115
|
+
if range_header:
|
|
116
|
+
logger.debug(
|
|
117
|
+
f"Range request for {file_type} PDF {pk}: {range_header}"
|
|
118
|
+
)
|
|
119
|
+
match = _RANGE_RE.match(range_header)
|
|
120
|
+
if match:
|
|
121
|
+
start = int(match.group(1))
|
|
122
|
+
end = int(match.group(2) or file_size - 1)
|
|
123
|
+
|
|
124
|
+
# Validate range
|
|
125
|
+
if start >= file_size or start < 0:
|
|
126
|
+
logger.warning(
|
|
127
|
+
f"Invalid range start {start} for file size {file_size}"
|
|
128
|
+
)
|
|
129
|
+
raise Http404("Invalid range")
|
|
130
|
+
|
|
131
|
+
if end >= file_size:
|
|
132
|
+
end = file_size - 1
|
|
133
|
+
|
|
134
|
+
chunk_size = end - start + 1
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
file_handle = open(file_path, "rb")
|
|
138
|
+
file_handle.seek(start)
|
|
139
|
+
|
|
140
|
+
logger.debug(
|
|
141
|
+
f"Serving {file_type} PDF {pk} range {start}-{end}/{file_size}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
response = StreamingHttpResponse(
|
|
145
|
+
ClosingFileWrapper(file_handle, blksize=8192),
|
|
146
|
+
status=206,
|
|
147
|
+
content_type="application/pdf",
|
|
148
|
+
)
|
|
149
|
+
response["Content-Length"] = str(chunk_size)
|
|
150
|
+
response["Content-Range"] = f"bytes {start}-{end}/{file_size}"
|
|
151
|
+
response["Accept-Ranges"] = "bytes"
|
|
152
|
+
response["Content-Disposition"] = (
|
|
153
|
+
f'inline; filename="{safe_filename}"'
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return response
|
|
157
|
+
except (OSError, IOError) as e:
|
|
158
|
+
logger.error(
|
|
159
|
+
f"Error opening {file_type} PDF file for range request: {e}"
|
|
160
|
+
)
|
|
161
|
+
raise Http404(f"Error accessing {file_type} PDF file")
|
|
162
|
+
else:
|
|
163
|
+
logger.warning(f"Invalid Range header format: {range_header}")
|
|
164
|
+
|
|
165
|
+
# Serve entire file using FileResponse (automatically handles file closing)
|
|
166
|
+
logger.debug(f"Serving full {file_type} PDF {pk} ({file_size} bytes)")
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
file_handle = open(file_path, "rb")
|
|
170
|
+
response = FileResponse(file_handle, content_type="application/pdf")
|
|
171
|
+
response["Content-Length"] = str(file_size)
|
|
172
|
+
response["Accept-Ranges"] = "bytes"
|
|
173
|
+
response["Content-Disposition"] = f'inline; filename="{safe_filename}"'
|
|
174
|
+
|
|
175
|
+
# FileResponse will take ownership of file_handle and close it after response
|
|
176
|
+
return response
|
|
177
|
+
except (OSError, IOError) as e:
|
|
178
|
+
logger.error(f"Error opening {file_type} PDF file: {e}")
|
|
179
|
+
raise Http404(f"Error accessing {file_type} PDF file")
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error(
|
|
183
|
+
f"Unexpected error streaming {file_type if 'file_type' in locals() else 'PDF'} {pk}: {e}",
|
|
184
|
+
exc_info=True,
|
|
185
|
+
)
|
|
186
|
+
raise Http404("Error streaming PDF")
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import logging
|
|
3
|
-
from django.http import FileResponse, StreamingHttpResponse, Http404
|
|
4
|
-
from rest_framework.views import APIView
|
|
5
|
-
from ...utils.permissions import EnvironmentAwarePermission
|
|
6
|
-
from endoreg_db.models import RawPdfFile
|
|
7
|
-
import os
|
|
8
|
-
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
|
9
|
-
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
11
|
-
_RANGE_RE = re.compile(r"bytes=(\d+)-(\d*)")
|
|
12
|
-
|
|
13
|
-
class ClosingFileWrapper:
|
|
14
|
-
"""Custom file wrapper that ensures file is closed after streaming"""
|
|
15
|
-
def __init__(self, file_handle, blksize=8192):
|
|
16
|
-
self.file_handle = file_handle
|
|
17
|
-
self.blksize = blksize
|
|
18
|
-
|
|
19
|
-
def __iter__(self):
|
|
20
|
-
return self
|
|
21
|
-
|
|
22
|
-
def __next__(self):
|
|
23
|
-
data = self.file_handle.read(self.blksize)
|
|
24
|
-
if not data:
|
|
25
|
-
self.file_handle.close()
|
|
26
|
-
raise StopIteration
|
|
27
|
-
return data
|
|
28
|
-
|
|
29
|
-
def close(self):
|
|
30
|
-
if hasattr(self.file_handle, 'close'):
|
|
31
|
-
self.file_handle.close()
|
|
32
|
-
|
|
33
|
-
class PDFStreamView(APIView):
|
|
34
|
-
"""
|
|
35
|
-
Streams a PDF file with correct HTTP range support and proper file handle management.
|
|
36
|
-
"""
|
|
37
|
-
permission_classes = [EnvironmentAwarePermission]
|
|
38
|
-
|
|
39
|
-
@xframe_options_sameorigin
|
|
40
|
-
def get(self, request, pdf_id: int, *args, **kwargs):
|
|
41
|
-
try:
|
|
42
|
-
pdf_obj = RawPdfFile.objects.filter(pk=pdf_id).first()
|
|
43
|
-
if not pdf_obj or not pdf_obj.file:
|
|
44
|
-
logger.warning(f"PDF not found: ID {pdf_id}")
|
|
45
|
-
raise Http404("PDF not found")
|
|
46
|
-
|
|
47
|
-
# Check if file exists on filesystem
|
|
48
|
-
try:
|
|
49
|
-
file_path = pdf_obj.file.path
|
|
50
|
-
if not os.path.exists(file_path):
|
|
51
|
-
logger.error(f"PDF file does not exist on filesystem: {file_path}")
|
|
52
|
-
raise Http404("PDF file not found on filesystem")
|
|
53
|
-
|
|
54
|
-
file_size = os.path.getsize(file_path)
|
|
55
|
-
except (OSError, IOError, AttributeError) as e:
|
|
56
|
-
logger.error(f"Error accessing PDF file {pdf_id}: {e}")
|
|
57
|
-
raise Http404("PDF file not accessible")
|
|
58
|
-
|
|
59
|
-
# Generate safe filename
|
|
60
|
-
safe_filename = os.path.basename(pdf_obj.file.name) if pdf_obj.file.name else f"document_{pdf_id}.pdf"
|
|
61
|
-
if not safe_filename.endswith('.pdf'):
|
|
62
|
-
safe_filename += '.pdf'
|
|
63
|
-
|
|
64
|
-
# Handle Range requests
|
|
65
|
-
range_header = request.headers.get("Range")
|
|
66
|
-
if range_header:
|
|
67
|
-
logger.debug(f"Range request for PDF {pdf_id}: {range_header}")
|
|
68
|
-
match = _RANGE_RE.match(range_header)
|
|
69
|
-
if match:
|
|
70
|
-
start = int(match.group(1))
|
|
71
|
-
end = int(match.group(2) or file_size - 1)
|
|
72
|
-
|
|
73
|
-
# Validate range
|
|
74
|
-
if start >= file_size or start < 0:
|
|
75
|
-
logger.warning(f"Invalid range start {start} for file size {file_size}")
|
|
76
|
-
raise Http404("Invalid range")
|
|
77
|
-
|
|
78
|
-
if end >= file_size:
|
|
79
|
-
end = file_size - 1
|
|
80
|
-
|
|
81
|
-
chunk_size = end - start + 1
|
|
82
|
-
|
|
83
|
-
try:
|
|
84
|
-
file_handle = open(file_path, "rb")
|
|
85
|
-
file_handle.seek(start)
|
|
86
|
-
|
|
87
|
-
logger.debug(f"Serving PDF {pdf_id} range {start}-{end}/{file_size}")
|
|
88
|
-
|
|
89
|
-
response = StreamingHttpResponse(
|
|
90
|
-
ClosingFileWrapper(file_handle, blksize=8192),
|
|
91
|
-
status=206,
|
|
92
|
-
content_type="application/pdf",
|
|
93
|
-
)
|
|
94
|
-
response["Content-Length"] = str(chunk_size)
|
|
95
|
-
response["Content-Range"] = f"bytes {start}-{end}/{file_size}"
|
|
96
|
-
response["Accept-Ranges"] = "bytes"
|
|
97
|
-
response["Content-Disposition"] = f'inline; filename="{safe_filename}"'
|
|
98
|
-
|
|
99
|
-
return response
|
|
100
|
-
except (OSError, IOError) as e:
|
|
101
|
-
logger.error(f"Error opening PDF file for range request: {e}")
|
|
102
|
-
raise Http404("Error accessing PDF file")
|
|
103
|
-
else:
|
|
104
|
-
logger.warning(f"Invalid Range header format: {range_header}")
|
|
105
|
-
|
|
106
|
-
# Serve entire file using FileResponse (automatically handles file closing)
|
|
107
|
-
logger.debug(f"Serving full PDF {pdf_id} ({file_size} bytes)")
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
file_handle = open(file_path, "rb")
|
|
111
|
-
response = FileResponse(
|
|
112
|
-
file_handle,
|
|
113
|
-
content_type="application/pdf"
|
|
114
|
-
)
|
|
115
|
-
response["Content-Length"] = str(file_size)
|
|
116
|
-
response["Accept-Ranges"] = "bytes"
|
|
117
|
-
response["Content-Disposition"] = f'inline; filename="{safe_filename}"'
|
|
118
|
-
|
|
119
|
-
# FileResponse will take ownership of file_handle and close it after response
|
|
120
|
-
return response
|
|
121
|
-
except (OSError, IOError) as e:
|
|
122
|
-
logger.error(f"Error opening PDF file: {e}")
|
|
123
|
-
raise Http404("Error accessing PDF file")
|
|
124
|
-
|
|
125
|
-
except Exception as e:
|
|
126
|
-
logger.error(f"Unexpected error streaming PDF {pdf_id}: {e}", exc_info=True)
|
|
127
|
-
raise Http404("Error streaming PDF")
|