endoreg-db 0.8.1__py3-none-any.whl → 0.8.2.1__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/helpers/download_segmentation_model.py +31 -0
- endoreg_db/migrations/0003_add_center_display_name.py +30 -0
- endoreg_db/models/administration/center/center.py +7 -1
- endoreg_db/models/media/pdf/raw_pdf.py +31 -26
- endoreg_db/models/media/video/create_from_file.py +26 -4
- endoreg_db/models/media/video/pipe_1.py +13 -1
- endoreg_db/models/media/video/video_file.py +36 -13
- endoreg_db/models/media/video/video_file_anonymize.py +2 -1
- endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +12 -0
- endoreg_db/models/media/video/video_file_io.py +4 -2
- endoreg_db/models/metadata/video_meta.py +2 -2
- endoreg_db/serializers/anonymization.py +3 -0
- endoreg_db/services/pdf_import.py +131 -45
- endoreg_db/services/video_import.py +427 -128
- endoreg_db/urls/__init__.py +0 -2
- endoreg_db/urls/media.py +201 -4
- endoreg_db/urls/report.py +0 -30
- endoreg_db/urls/sensitive_meta.py +0 -36
- endoreg_db/urls/video.py +30 -88
- endoreg_db/utils/paths.py +2 -10
- endoreg_db/utils/video/ffmpeg_wrapper.py +67 -4
- endoreg_db/views/anonymization/validate.py +76 -32
- endoreg_db/views/media/__init__.py +38 -2
- endoreg_db/views/media/pdf_media.py +1 -1
- endoreg_db/views/media/segments.py +71 -0
- endoreg_db/views/media/sensitive_metadata.py +314 -0
- endoreg_db/views/media/video_segments.py +596 -0
- endoreg_db/views/pdf/reimport.py +18 -8
- endoreg_db/views/video/__init__.py +0 -8
- endoreg_db/views/video/correction.py +34 -32
- endoreg_db/views/video/reimport.py +15 -12
- endoreg_db/views/video/video_stream.py +168 -50
- {endoreg_db-0.8.1.dist-info → endoreg_db-0.8.2.1.dist-info}/METADATA +2 -2
- {endoreg_db-0.8.1.dist-info → endoreg_db-0.8.2.1.dist-info}/RECORD +47 -43
- endoreg_db/views/video/media/__init__.py +0 -23
- /endoreg_db/{urls/pdf.py → config/__init__.py} +0 -0
- /endoreg_db/views/video/{media/task_status.py → task_status.py} +0 -0
- /endoreg_db/views/video/{media/video_analyze.py → video_analyze.py} +0 -0
- /endoreg_db/views/video/{media/video_apply_mask.py → video_apply_mask.py} +0 -0
- /endoreg_db/views/video/{media/video_correction.py → video_correction.py} +0 -0
- /endoreg_db/views/video/{media/video_download_processed.py → video_download_processed.py} +0 -0
- /endoreg_db/views/video/{media/video_media.py → video_media.py} +0 -0
- /endoreg_db/views/video/{media/video_meta.py → video_meta.py} +0 -0
- /endoreg_db/views/video/{media/video_processing_history.py → video_processing_history.py} +0 -0
- /endoreg_db/views/video/{media/video_remove_frames.py → video_remove_frames.py} +0 -0
- /endoreg_db/views/video/{media/video_reprocess.py → video_reprocess.py} +0 -0
- {endoreg_db-0.8.1.dist-info → endoreg_db-0.8.2.1.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.1.dist-info → endoreg_db-0.8.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, cast
|
|
3
|
+
|
|
4
|
+
from django.db import transaction
|
|
1
5
|
from rest_framework import status
|
|
2
6
|
from rest_framework.response import Response
|
|
3
7
|
from rest_framework.views import APIView
|
|
4
|
-
|
|
5
|
-
from endoreg_db.models import
|
|
8
|
+
|
|
9
|
+
from endoreg_db.models import RawPdfFile, VideoFile
|
|
6
10
|
from endoreg_db.serializers.anonymization import SensitiveMetaValidateSerializer
|
|
7
11
|
|
|
8
12
|
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
9
16
|
class AnonymizationValidateView(APIView):
|
|
10
17
|
"""
|
|
11
18
|
POST /api/anonymization/<int:file_id>/validate/
|
|
@@ -21,6 +28,7 @@ class AnonymizationValidateView(APIView):
|
|
|
21
28
|
"casenumber": "12345",
|
|
22
29
|
"anonymized_text": "...", // nur für PDFs; Videos ignorieren
|
|
23
30
|
"is_verified": true // optional; default true
|
|
31
|
+
"file_type": "video" // optional; "video" oder "pdf"; wenn nicht angegeben, wird zuerst Video, dann PDF versucht
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
Rückwärtskompatibilität: ISO-Format (YYYY-MM-DD) wird ebenfalls akzeptiert.
|
|
@@ -31,33 +39,69 @@ class AnonymizationValidateView(APIView):
|
|
|
31
39
|
# Serializer-Validierung mit deutscher Datums-Priorität
|
|
32
40
|
serializer = SensitiveMetaValidateSerializer(data=request.data or {})
|
|
33
41
|
serializer.is_valid(raise_exception=True)
|
|
34
|
-
|
|
35
|
-
payload
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
42
|
+
validated_data = cast(Dict[str, Any], serializer.validated_data)
|
|
43
|
+
payload: Dict[str, Any] = dict(validated_data)
|
|
44
|
+
if "is_verified" not in payload:
|
|
45
|
+
payload["is_verified"] = True
|
|
46
|
+
|
|
47
|
+
file_type = payload.get("file_type")
|
|
48
|
+
|
|
49
|
+
# Try Video first (unless explicitly requesting PDF)
|
|
50
|
+
if file_type in (None, "video"):
|
|
51
|
+
video = VideoFile.objects.select_related("center").filter(pk=file_id).first()
|
|
52
|
+
if video is not None:
|
|
53
|
+
prepared_payload = self._prepare_payload(payload, video)
|
|
54
|
+
try:
|
|
55
|
+
ok = video.validate_metadata_annotation(prepared_payload)
|
|
56
|
+
except Exception: # pragma: no cover - defensive safety net
|
|
57
|
+
logger.exception("Video validation crashed for id=%s", file_id)
|
|
58
|
+
return Response(
|
|
59
|
+
{"error": "Video validation encountered an unexpected error."},
|
|
60
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if not ok:
|
|
64
|
+
return Response({"error": "Video validation failed."}, status=status.HTTP_400_BAD_REQUEST)
|
|
65
|
+
|
|
66
|
+
return Response({"message": "Video validated."}, status=status.HTTP_200_OK)
|
|
67
|
+
|
|
68
|
+
if file_type == "video":
|
|
69
|
+
return Response({"error": f"Video {file_id} not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
70
|
+
|
|
71
|
+
# Then PDF (unless explicitly requesting Video)
|
|
72
|
+
if file_type in (None, "pdf"):
|
|
73
|
+
pdf = RawPdfFile.objects.select_related("center").filter(pk=file_id).first()
|
|
74
|
+
if pdf is not None:
|
|
75
|
+
prepared_payload = self._prepare_payload(payload, pdf)
|
|
76
|
+
try:
|
|
77
|
+
ok = pdf.validate_metadata_annotation(prepared_payload)
|
|
78
|
+
except Exception: # pragma: no cover - defensive safety net
|
|
79
|
+
logger.exception("PDF validation crashed for id=%s", file_id)
|
|
80
|
+
return Response(
|
|
81
|
+
{"error": "PDF validation encountered an unexpected error."},
|
|
82
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if not ok:
|
|
86
|
+
return Response({"error": "PDF validation failed."}, status=status.HTTP_400_BAD_REQUEST)
|
|
87
|
+
|
|
88
|
+
return Response({"message": "PDF validated."}, status=status.HTTP_200_OK)
|
|
89
|
+
|
|
90
|
+
if file_type == "pdf":
|
|
91
|
+
return Response({"error": f"PDF {file_id} not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
92
|
+
|
|
93
|
+
return Response({"error": f"Item {file_id} not found as video or pdf."}, status=status.HTTP_404_NOT_FOUND)
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _prepare_payload(base_payload: Dict[str, Any], file_obj: Any) -> Dict[str, Any]:
|
|
97
|
+
"""Return a fresh payload tailored for the given file object."""
|
|
98
|
+
|
|
99
|
+
prepared = dict(base_payload)
|
|
100
|
+
prepared.pop("file_type", None)
|
|
101
|
+
|
|
102
|
+
center = getattr(file_obj, "center", None)
|
|
103
|
+
center_name = getattr(center, "name", None)
|
|
104
|
+
if center_name and not prepared.get("center_name"):
|
|
105
|
+
prepared["center_name"] = center_name
|
|
106
|
+
|
|
107
|
+
return prepared
|
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
# Media Management Views (Phase 1.2)
|
|
2
2
|
|
|
3
3
|
from .video_media import VideoMediaView
|
|
4
|
-
from .pdf_media import
|
|
4
|
+
from .pdf_media import PdfMediaView
|
|
5
|
+
from ..video.reimport import VideoReimportView
|
|
6
|
+
from ..pdf.reimport import PdfReimportView
|
|
7
|
+
from .segments import video_segments_by_pk
|
|
8
|
+
from .video_segments import (
|
|
9
|
+
video_segments_collection,
|
|
10
|
+
video_segments_by_video,
|
|
11
|
+
video_segment_detail,
|
|
12
|
+
video_segments_stats,
|
|
13
|
+
video_segment_validate,
|
|
14
|
+
video_segments_validate_bulk,
|
|
15
|
+
video_segments_validation_status,
|
|
16
|
+
)
|
|
17
|
+
from .sensitive_metadata import (
|
|
18
|
+
video_sensitive_metadata,
|
|
19
|
+
video_sensitive_metadata_verify,
|
|
20
|
+
pdf_sensitive_metadata,
|
|
21
|
+
pdf_sensitive_metadata_verify,
|
|
22
|
+
sensitive_metadata_list,
|
|
23
|
+
pdf_sensitive_metadata_list,
|
|
24
|
+
)
|
|
5
25
|
|
|
6
26
|
__all__ = [
|
|
7
27
|
'VideoMediaView',
|
|
8
|
-
'
|
|
28
|
+
'PdfMediaView',
|
|
29
|
+
'VideoReimportView',
|
|
30
|
+
'PdfReimportView',
|
|
31
|
+
'video_segments_by_pk',
|
|
32
|
+
'video_segments_collection',
|
|
33
|
+
'video_segments_by_video',
|
|
34
|
+
'video_segment_detail',
|
|
35
|
+
'video_segments_stats',
|
|
36
|
+
'video_segment_validate',
|
|
37
|
+
'video_segments_validate_bulk',
|
|
38
|
+
'video_segments_validation_status',
|
|
39
|
+
'video_sensitive_metadata',
|
|
40
|
+
'video_sensitive_metadata_verify',
|
|
41
|
+
'pdf_sensitive_metadata',
|
|
42
|
+
'pdf_sensitive_metadata_verify',
|
|
43
|
+
'sensitive_metadata_list',
|
|
44
|
+
'pdf_sensitive_metadata_list',
|
|
9
45
|
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modern Media Framework - Video Segment API Views
|
|
3
|
+
October 14, 2025 - Migration to unified /api/media/videos/<pk>/segments/ pattern
|
|
4
|
+
|
|
5
|
+
This module provides modern framework views for video segment management,
|
|
6
|
+
wrapping legacy segment views with pk-based parameter handling.
|
|
7
|
+
"""
|
|
8
|
+
from endoreg_db.models import Label, LabelVideoSegment, VideoFile
|
|
9
|
+
from endoreg_db.serializers.label_video_segment.label_video_segment import LabelVideoSegmentSerializer
|
|
10
|
+
|
|
11
|
+
from django.db import transaction
|
|
12
|
+
from rest_framework import status
|
|
13
|
+
from rest_framework.decorators import api_view, permission_classes
|
|
14
|
+
from rest_framework.response import Response
|
|
15
|
+
|
|
16
|
+
from endoreg_db.utils.permissions import EnvironmentAwarePermission
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@api_view(['GET'])
|
|
23
|
+
@permission_classes([EnvironmentAwarePermission])
|
|
24
|
+
def video_segments_by_pk(request, pk):
|
|
25
|
+
"""
|
|
26
|
+
Modern media framework endpoint for retrieving video segments.
|
|
27
|
+
|
|
28
|
+
GET /api/media/videos/<int:pk>/segments/?label=<label_name>
|
|
29
|
+
|
|
30
|
+
Returns all segments for a video, optionally filtered by label name.
|
|
31
|
+
This is the modern replacement for /api/video/<id>/segments/
|
|
32
|
+
|
|
33
|
+
Query Parameters:
|
|
34
|
+
label (str, optional): Filter segments by label name (e.g., 'outside')
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
200: List of video segments
|
|
38
|
+
404: Video not found
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
video = VideoFile.objects.get(id=pk)
|
|
42
|
+
except VideoFile.DoesNotExist:
|
|
43
|
+
logger.warning(f"Video with pk {pk} not found")
|
|
44
|
+
return Response(
|
|
45
|
+
{'error': f'Video with id {pk} not found'},
|
|
46
|
+
status=status.HTTP_404_NOT_FOUND
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Start with all segments for this video
|
|
50
|
+
queryset = LabelVideoSegment.objects.filter(video_file=video)
|
|
51
|
+
|
|
52
|
+
# Optional filtering by label name
|
|
53
|
+
label_name = request.GET.get('label')
|
|
54
|
+
if label_name:
|
|
55
|
+
try:
|
|
56
|
+
label = Label.objects.get(name=label_name)
|
|
57
|
+
queryset = queryset.filter(label=label)
|
|
58
|
+
logger.info(f"Filtering segments for video {pk} by label '{label_name}'")
|
|
59
|
+
except Label.DoesNotExist:
|
|
60
|
+
logger.warning(f"Label '{label_name}' not found, returning empty result")
|
|
61
|
+
return Response(
|
|
62
|
+
{'error': f"Label '{label_name}' not found"},
|
|
63
|
+
status=status.HTTP_404_NOT_FOUND
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Order by start time for consistent results
|
|
67
|
+
segments = queryset.order_by('start_frame_number')
|
|
68
|
+
serializer = LabelVideoSegmentSerializer(segments, many=True)
|
|
69
|
+
|
|
70
|
+
logger.info(f"Returning {len(segments)} segments for video {pk}")
|
|
71
|
+
return Response(serializer.data)
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Modern Media Framework: Sensitive Metadata Management
|
|
2
|
+
from rest_framework.decorators import api_view, permission_classes
|
|
3
|
+
from rest_framework.response import Response
|
|
4
|
+
from rest_framework import status
|
|
5
|
+
from django.db import transaction
|
|
6
|
+
from django.db.models import Q
|
|
7
|
+
from django.shortcuts import get_object_or_404
|
|
8
|
+
from endoreg_db.utils.permissions import EnvironmentAwarePermission
|
|
9
|
+
from endoreg_db.models import VideoFile, RawPdfFile, SensitiveMeta
|
|
10
|
+
from endoreg_db.serializers.meta import (
|
|
11
|
+
SensitiveMetaDetailSerializer,
|
|
12
|
+
SensitiveMetaUpdateSerializer,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# === VIDEO SENSITIVE METADATA ===
|
|
16
|
+
|
|
17
|
+
@api_view(['GET', 'PATCH'])
|
|
18
|
+
@permission_classes([EnvironmentAwarePermission])
|
|
19
|
+
def video_sensitive_metadata(request, pk):
|
|
20
|
+
"""
|
|
21
|
+
GET /api/media/videos/<pk>/sensitive-metadata/
|
|
22
|
+
PATCH /api/media/videos/<pk>/sensitive-metadata/
|
|
23
|
+
|
|
24
|
+
Get or update sensitive metadata for a video.
|
|
25
|
+
Video-scoped: Uses video ID to locate related sensitive metadata.
|
|
26
|
+
"""
|
|
27
|
+
video = get_object_or_404(VideoFile, pk=pk)
|
|
28
|
+
|
|
29
|
+
# Get related sensitive metadata
|
|
30
|
+
if not video.sensitive_meta:
|
|
31
|
+
return Response(
|
|
32
|
+
{"error": f"No sensitive metadata found for video {pk}"},
|
|
33
|
+
status=status.HTTP_404_NOT_FOUND
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
sensitive_meta = video.sensitive_meta
|
|
37
|
+
|
|
38
|
+
if request.method == 'GET':
|
|
39
|
+
serializer = SensitiveMetaDetailSerializer(sensitive_meta)
|
|
40
|
+
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
41
|
+
|
|
42
|
+
elif request.method == 'PATCH':
|
|
43
|
+
serializer = SensitiveMetaUpdateSerializer(
|
|
44
|
+
sensitive_meta,
|
|
45
|
+
data=request.data,
|
|
46
|
+
partial=True
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if serializer.is_valid():
|
|
50
|
+
updated_instance = serializer.save()
|
|
51
|
+
response_serializer = SensitiveMetaDetailSerializer(updated_instance)
|
|
52
|
+
|
|
53
|
+
return Response({
|
|
54
|
+
"message": "Sensitive metadata updated successfully",
|
|
55
|
+
"sensitive_meta": response_serializer.data,
|
|
56
|
+
"video_id": pk
|
|
57
|
+
}, status=status.HTTP_200_OK)
|
|
58
|
+
|
|
59
|
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@api_view(['POST'])
|
|
63
|
+
@permission_classes([EnvironmentAwarePermission])
|
|
64
|
+
@transaction.atomic
|
|
65
|
+
def video_sensitive_metadata_verify(request, pk):
|
|
66
|
+
"""
|
|
67
|
+
POST /api/media/videos/<pk>/sensitive-metadata/verify/
|
|
68
|
+
|
|
69
|
+
Update verification state for video sensitive metadata.
|
|
70
|
+
|
|
71
|
+
Expected payload:
|
|
72
|
+
{
|
|
73
|
+
"dob_verified": true,
|
|
74
|
+
"names_verified": true
|
|
75
|
+
}
|
|
76
|
+
"""
|
|
77
|
+
video = get_object_or_404(VideoFile, pk=pk)
|
|
78
|
+
|
|
79
|
+
if not video.sensitive_meta:
|
|
80
|
+
return Response(
|
|
81
|
+
{"error": f"No sensitive metadata found for video {pk}"},
|
|
82
|
+
status=status.HTTP_404_NOT_FOUND
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
sensitive_meta = video.sensitive_meta
|
|
86
|
+
|
|
87
|
+
dob_verified = request.data.get('dob_verified')
|
|
88
|
+
names_verified = request.data.get('names_verified')
|
|
89
|
+
|
|
90
|
+
if dob_verified is None and names_verified is None:
|
|
91
|
+
return Response(
|
|
92
|
+
{"error": "At least one of dob_verified or names_verified must be provided"},
|
|
93
|
+
status=status.HTTP_400_BAD_REQUEST
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
state = sensitive_meta.get_or_create_state()
|
|
97
|
+
|
|
98
|
+
if dob_verified is not None:
|
|
99
|
+
state.dob_verified = dob_verified
|
|
100
|
+
if names_verified is not None:
|
|
101
|
+
state.names_verified = names_verified
|
|
102
|
+
|
|
103
|
+
state.save()
|
|
104
|
+
|
|
105
|
+
response_serializer = SensitiveMetaDetailSerializer(sensitive_meta)
|
|
106
|
+
return Response({
|
|
107
|
+
"message": "Verification state updated successfully",
|
|
108
|
+
"sensitive_meta": response_serializer.data,
|
|
109
|
+
"video_id": pk,
|
|
110
|
+
"state_verified": state.is_verified
|
|
111
|
+
}, status=status.HTTP_200_OK)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# === PDF SENSITIVE METADATA ===
|
|
115
|
+
|
|
116
|
+
@api_view(['GET', 'PATCH'])
|
|
117
|
+
@permission_classes([EnvironmentAwarePermission])
|
|
118
|
+
def pdf_sensitive_metadata(request, pk):
|
|
119
|
+
"""
|
|
120
|
+
GET /api/media/pdfs/<pk>/sensitive-metadata/
|
|
121
|
+
PATCH /api/media/pdfs/<pk>/sensitive-metadata/
|
|
122
|
+
|
|
123
|
+
Get or update sensitive metadata for a PDF.
|
|
124
|
+
PDF-scoped: Uses PDF ID to locate related sensitive metadata.
|
|
125
|
+
"""
|
|
126
|
+
pdf = get_object_or_404(RawPdfFile, pk=pk)
|
|
127
|
+
|
|
128
|
+
# Get related sensitive metadata
|
|
129
|
+
if not pdf.sensitive_meta:
|
|
130
|
+
return Response(
|
|
131
|
+
{"error": f"No sensitive metadata found for PDF {pk}"},
|
|
132
|
+
status=status.HTTP_404_NOT_FOUND
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
sensitive_meta = pdf.sensitive_meta
|
|
136
|
+
|
|
137
|
+
if request.method == 'GET':
|
|
138
|
+
serializer = SensitiveMetaDetailSerializer(sensitive_meta)
|
|
139
|
+
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
140
|
+
|
|
141
|
+
elif request.method == 'PATCH':
|
|
142
|
+
serializer = SensitiveMetaUpdateSerializer(
|
|
143
|
+
sensitive_meta,
|
|
144
|
+
data=request.data,
|
|
145
|
+
partial=True
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if serializer.is_valid():
|
|
149
|
+
updated_instance = serializer.save()
|
|
150
|
+
response_serializer = SensitiveMetaDetailSerializer(updated_instance)
|
|
151
|
+
|
|
152
|
+
return Response({
|
|
153
|
+
"message": "Sensitive metadata updated successfully",
|
|
154
|
+
"sensitive_meta": response_serializer.data,
|
|
155
|
+
"pdf_id": pk
|
|
156
|
+
}, status=status.HTTP_200_OK)
|
|
157
|
+
|
|
158
|
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@api_view(['POST'])
|
|
162
|
+
@permission_classes([EnvironmentAwarePermission])
|
|
163
|
+
@transaction.atomic
|
|
164
|
+
def pdf_sensitive_metadata_verify(request, pk):
|
|
165
|
+
"""
|
|
166
|
+
POST /api/media/pdfs/<pk>/sensitive-metadata/verify/
|
|
167
|
+
|
|
168
|
+
Update verification state for PDF sensitive metadata.
|
|
169
|
+
|
|
170
|
+
Expected payload:
|
|
171
|
+
{
|
|
172
|
+
"dob_verified": true,
|
|
173
|
+
"names_verified": true
|
|
174
|
+
}
|
|
175
|
+
"""
|
|
176
|
+
pdf = get_object_or_404(RawPdfFile, pk=pk)
|
|
177
|
+
|
|
178
|
+
if not pdf.sensitive_meta:
|
|
179
|
+
return Response(
|
|
180
|
+
{"error": f"No sensitive metadata found for PDF {pk}"},
|
|
181
|
+
status=status.HTTP_404_NOT_FOUND
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
sensitive_meta = pdf.sensitive_meta
|
|
185
|
+
|
|
186
|
+
dob_verified = request.data.get('dob_verified')
|
|
187
|
+
names_verified = request.data.get('names_verified')
|
|
188
|
+
|
|
189
|
+
if dob_verified is None and names_verified is None:
|
|
190
|
+
return Response(
|
|
191
|
+
{"error": "At least one of dob_verified or names_verified must be provided"},
|
|
192
|
+
status=status.HTTP_400_BAD_REQUEST
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
state = sensitive_meta.get_or_create_state()
|
|
196
|
+
|
|
197
|
+
if dob_verified is not None:
|
|
198
|
+
state.dob_verified = dob_verified
|
|
199
|
+
if names_verified is not None:
|
|
200
|
+
state.names_verified = names_verified
|
|
201
|
+
|
|
202
|
+
state.save()
|
|
203
|
+
|
|
204
|
+
response_serializer = SensitiveMetaDetailSerializer(sensitive_meta)
|
|
205
|
+
return Response({
|
|
206
|
+
"message": "Verification state updated successfully",
|
|
207
|
+
"sensitive_meta": response_serializer.data,
|
|
208
|
+
"pdf_id": pk,
|
|
209
|
+
"state_verified": state.is_verified
|
|
210
|
+
}, status=status.HTTP_200_OK)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# === LIST ENDPOINTS (Collection-Level) ===
|
|
214
|
+
|
|
215
|
+
@api_view(['GET'])
|
|
216
|
+
@permission_classes([EnvironmentAwarePermission])
|
|
217
|
+
def sensitive_metadata_list(request):
|
|
218
|
+
"""
|
|
219
|
+
GET /api/media/sensitive-metadata/
|
|
220
|
+
|
|
221
|
+
List all sensitive metadata (combined PDFs and Videos).
|
|
222
|
+
Supports filtering by content_type, status, etc.
|
|
223
|
+
|
|
224
|
+
Query parameters:
|
|
225
|
+
- content_type: 'pdf' | 'video' (optional)
|
|
226
|
+
- verified: Filter by verification status
|
|
227
|
+
- ordering: Sort field
|
|
228
|
+
- search: Search in patient names
|
|
229
|
+
"""
|
|
230
|
+
from endoreg_db.serializers.meta import SensitiveMetaDetailSerializer
|
|
231
|
+
|
|
232
|
+
# Get all sensitive metadata
|
|
233
|
+
queryset = SensitiveMeta.objects.select_related('state').all()
|
|
234
|
+
|
|
235
|
+
# Filter by content type
|
|
236
|
+
content_type = request.query_params.get('content_type')
|
|
237
|
+
if content_type == 'pdf':
|
|
238
|
+
# Only PDFs - filter by existence of related PDFs
|
|
239
|
+
queryset = queryset.filter(raw_pdf_files__isnull=False).distinct()
|
|
240
|
+
elif content_type == 'video':
|
|
241
|
+
# Only Videos - filter by existence of related video
|
|
242
|
+
queryset = queryset.filter(video_file__isnull=False).distinct()
|
|
243
|
+
|
|
244
|
+
# Filter by verification status
|
|
245
|
+
verified = request.query_params.get('verified')
|
|
246
|
+
if verified is not None:
|
|
247
|
+
verified_bool = verified.lower() in ('true', '1', 'yes')
|
|
248
|
+
queryset = queryset.filter(state__is_verified=verified_bool)
|
|
249
|
+
|
|
250
|
+
# Search in patient names
|
|
251
|
+
search = request.query_params.get('search')
|
|
252
|
+
if search:
|
|
253
|
+
queryset = queryset.filter(
|
|
254
|
+
Q(patient_first_name__icontains=search) |
|
|
255
|
+
Q(patient_last_name__icontains=search)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Ordering
|
|
259
|
+
ordering = request.query_params.get('ordering', '-id')
|
|
260
|
+
queryset = queryset.order_by(ordering)
|
|
261
|
+
|
|
262
|
+
# Pagination
|
|
263
|
+
from rest_framework.pagination import PageNumberPagination
|
|
264
|
+
paginator = PageNumberPagination()
|
|
265
|
+
paginator.page_size = 20
|
|
266
|
+
page = paginator.paginate_queryset(queryset, request)
|
|
267
|
+
|
|
268
|
+
if page is not None:
|
|
269
|
+
serializer = SensitiveMetaDetailSerializer(page, many=True)
|
|
270
|
+
return paginator.get_paginated_response(serializer.data)
|
|
271
|
+
|
|
272
|
+
serializer = SensitiveMetaDetailSerializer(queryset, many=True)
|
|
273
|
+
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@api_view(['GET'])
|
|
277
|
+
@permission_classes([EnvironmentAwarePermission])
|
|
278
|
+
def pdf_sensitive_metadata_list(request):
|
|
279
|
+
"""
|
|
280
|
+
GET /api/media/pdfs/sensitive-metadata/
|
|
281
|
+
|
|
282
|
+
List sensitive metadata for PDFs only.
|
|
283
|
+
Replaces legacy /api/pdf/sensitivemeta/list/
|
|
284
|
+
"""
|
|
285
|
+
from endoreg_db.serializers.meta import SensitiveMetaDetailSerializer
|
|
286
|
+
|
|
287
|
+
# Get all PDFs with sensitive metadata
|
|
288
|
+
queryset = SensitiveMeta.objects.select_related('state').filter(
|
|
289
|
+
raw_pdf_files__isnull=False
|
|
290
|
+
).distinct()
|
|
291
|
+
|
|
292
|
+
# Apply filters
|
|
293
|
+
search = request.query_params.get('search')
|
|
294
|
+
if search:
|
|
295
|
+
queryset = queryset.filter(
|
|
296
|
+
Q(patient_first_name__icontains=search) |
|
|
297
|
+
Q(patient_last_name__icontains=search)
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
ordering = request.query_params.get('ordering', '-id')
|
|
301
|
+
queryset = queryset.order_by(ordering)
|
|
302
|
+
|
|
303
|
+
# Pagination
|
|
304
|
+
from rest_framework.pagination import PageNumberPagination
|
|
305
|
+
paginator = PageNumberPagination()
|
|
306
|
+
paginator.page_size = 20
|
|
307
|
+
page = paginator.paginate_queryset(queryset, request)
|
|
308
|
+
|
|
309
|
+
if page is not None:
|
|
310
|
+
serializer = SensitiveMetaDetailSerializer(page, many=True)
|
|
311
|
+
return paginator.get_paginated_response(serializer.data)
|
|
312
|
+
|
|
313
|
+
serializer = SensitiveMetaDetailSerializer(queryset, many=True)
|
|
314
|
+
return Response(serializer.data, status=status.HTTP_200_OK)
|