endoreg-db 0.8.3.3__py3-none-any.whl → 0.8.6.5__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/data/ai_model_meta/default_multilabel_classification.yaml +23 -1
- endoreg_db/data/setup_config.yaml +38 -0
- endoreg_db/management/commands/create_model_meta_from_huggingface.py +1 -2
- endoreg_db/management/commands/load_ai_model_data.py +18 -15
- endoreg_db/management/commands/setup_endoreg_db.py +218 -33
- endoreg_db/models/media/pdf/raw_pdf.py +241 -97
- endoreg_db/models/media/video/pipe_1.py +30 -33
- endoreg_db/models/media/video/video_file.py +300 -187
- endoreg_db/models/medical/hardware/endoscopy_processor.py +10 -1
- endoreg_db/models/metadata/model_meta_logic.py +34 -45
- endoreg_db/models/metadata/sensitive_meta_logic.py +555 -150
- endoreg_db/serializers/__init__.py +26 -55
- endoreg_db/serializers/misc/__init__.py +1 -1
- endoreg_db/serializers/misc/file_overview.py +65 -35
- endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
- endoreg_db/serializers/video_examination.py +198 -0
- endoreg_db/services/lookup_service.py +228 -58
- endoreg_db/services/lookup_store.py +174 -30
- endoreg_db/services/pdf_import.py +585 -282
- endoreg_db/services/video_import.py +493 -240
- endoreg_db/urls/__init__.py +36 -23
- endoreg_db/urls/label_video_segments.py +2 -0
- endoreg_db/urls/media.py +103 -66
- endoreg_db/utils/setup_config.py +177 -0
- endoreg_db/views/__init__.py +5 -3
- endoreg_db/views/media/pdf_media.py +3 -1
- endoreg_db/views/media/video_media.py +1 -1
- endoreg_db/views/media/video_segments.py +187 -259
- endoreg_db/views/pdf/__init__.py +5 -8
- endoreg_db/views/pdf/pdf_stream.py +186 -0
- endoreg_db/views/pdf/reimport.py +110 -94
- endoreg_db/views/requirement/lookup.py +171 -287
- endoreg_db/views/video/__init__.py +0 -2
- endoreg_db/views/video/video_examination_viewset.py +202 -289
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.dist-info}/METADATA +1 -2
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.dist-info}/RECORD +38 -37
- endoreg_db/views/pdf/pdf_media.py +0 -239
- endoreg_db/views/pdf/pdf_stream_views.py +0 -127
- endoreg_db/views/video/video_media.py +0 -158
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.3.3.dist-info → endoreg_db-0.8.6.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,329 +1,242 @@
|
|
|
1
|
-
# endoreg_db/views/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# endoreg_db/views/video/video_examination_viewset.py
|
|
2
|
+
"""
|
|
3
|
+
Video Examination ViewSet
|
|
4
|
+
|
|
5
|
+
Provides REST API endpoints for managing video-based patient examinations.
|
|
6
|
+
Handles CRUD operations for PatientExamination records linked to VideoFile instances.
|
|
7
|
+
|
|
8
|
+
**API Endpoints:**
|
|
9
|
+
- GET /api/video-examinations/ - List all video examinations
|
|
10
|
+
- GET /api/video-examinations/{id}/ - Get examination details
|
|
11
|
+
- POST /api/video-examinations/ - Create new examination
|
|
12
|
+
- PATCH /api/video-examinations/{id}/ - Update examination
|
|
13
|
+
- DELETE /api/video-examinations/{id}/ - Delete examination
|
|
14
|
+
- GET /api/video/{video_id}/examinations/ - List examinations for specific video
|
|
15
|
+
|
|
16
|
+
**Frontend Integration:**
|
|
17
|
+
Used by VideoExaminationAnnotation.vue for annotation workflow.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
|
|
22
|
+
from django.db import transaction
|
|
6
23
|
from django.shortcuts import get_object_or_404
|
|
7
|
-
from rest_framework import viewsets
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
FindingClassification,
|
|
13
|
-
FindingClassificationChoice,
|
|
14
|
-
FindingIntervention,
|
|
15
|
-
Finding
|
|
16
|
-
)
|
|
17
|
-
from ...serializers.examination import (
|
|
18
|
-
ExaminationSerializer as ExaminationSerializer,
|
|
19
|
-
# FindingSerializer as FindingSerializer,
|
|
20
|
-
)
|
|
24
|
+
from rest_framework import status, viewsets
|
|
25
|
+
from rest_framework.decorators import action
|
|
26
|
+
from rest_framework.response import Response
|
|
27
|
+
|
|
28
|
+
from endoreg_db.models import PatientExamination, VideoFile
|
|
21
29
|
|
|
30
|
+
from ...serializers.video_examination import (
|
|
31
|
+
VideoExaminationCreateSerializer,
|
|
32
|
+
VideoExaminationSerializer,
|
|
33
|
+
VideoExaminationUpdateSerializer,
|
|
34
|
+
)
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
queryset = Examination.objects.all()
|
|
25
|
-
serializer_class = ExaminationSerializer
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
26
37
|
|
|
27
38
|
|
|
28
|
-
# NEW: Video Examination CRUD ViewSet
|
|
29
39
|
class VideoExaminationViewSet(viewsets.ModelViewSet):
|
|
30
40
|
"""
|
|
31
|
-
ViewSet for Video Examination CRUD operations
|
|
32
|
-
|
|
41
|
+
ViewSet for Video Examination CRUD operations.
|
|
42
|
+
|
|
43
|
+
Provides comprehensive API for managing patient examinations within
|
|
44
|
+
the video annotation workflow. Supports filtering by video, patient,
|
|
45
|
+
and examination type.
|
|
46
|
+
|
|
47
|
+
**Usage Example:**
|
|
48
|
+
```python
|
|
49
|
+
# Frontend (JavaScript)
|
|
50
|
+
// Get examinations for video 123
|
|
51
|
+
const response = await api.get('/api/video/123/examinations/');
|
|
52
|
+
|
|
53
|
+
// Create new examination
|
|
54
|
+
await api.post('/api/video-examinations/', {
|
|
55
|
+
video_id: 123,
|
|
56
|
+
examination_id: 5,
|
|
57
|
+
date_start: '2024-01-15'
|
|
58
|
+
});
|
|
59
|
+
```
|
|
33
60
|
"""
|
|
34
|
-
|
|
61
|
+
|
|
62
|
+
queryset = PatientExamination.objects.select_related(
|
|
63
|
+
"patient", "examination", "video"
|
|
64
|
+
).prefetch_related("patient_findings")
|
|
65
|
+
serializer_class = VideoExaminationSerializer
|
|
66
|
+
|
|
67
|
+
def get_serializer_class(self):
|
|
68
|
+
"""
|
|
69
|
+
Return appropriate serializer based on action.
|
|
70
|
+
|
|
71
|
+
- create: VideoExaminationCreateSerializer (handles complex creation logic)
|
|
72
|
+
- update/partial_update: VideoExaminationUpdateSerializer
|
|
73
|
+
- list/retrieve: VideoExaminationSerializer (read-only with nested data)
|
|
74
|
+
"""
|
|
75
|
+
if self.action == "create":
|
|
76
|
+
return VideoExaminationCreateSerializer
|
|
77
|
+
elif self.action in ["update", "partial_update"]:
|
|
78
|
+
return VideoExaminationUpdateSerializer
|
|
79
|
+
return VideoExaminationSerializer
|
|
80
|
+
|
|
35
81
|
def get_queryset(self):
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
82
|
+
"""
|
|
83
|
+
Filter examinations based on query parameters.
|
|
84
|
+
|
|
85
|
+
**Supported filters:**
|
|
86
|
+
- ?video_id=123 - Get examinations for specific video
|
|
87
|
+
- ?patient_id=456 - Get examinations for specific patient
|
|
88
|
+
- ?examination_id=789 - Get examinations of specific type
|
|
89
|
+
"""
|
|
90
|
+
queryset = super().get_queryset()
|
|
91
|
+
|
|
92
|
+
# Filter by video if provided
|
|
93
|
+
video_id = self.request.query_params.get("video_id")
|
|
94
|
+
if video_id:
|
|
95
|
+
queryset = queryset.filter(video_id=video_id)
|
|
96
|
+
|
|
97
|
+
# Filter by patient if provided
|
|
98
|
+
patient_id = self.request.query_params.get("patient_id")
|
|
99
|
+
if patient_id:
|
|
100
|
+
queryset = queryset.filter(patient_id=patient_id)
|
|
101
|
+
|
|
102
|
+
# Filter by examination type if provided
|
|
103
|
+
examination_id = self.request.query_params.get("examination_id")
|
|
104
|
+
if examination_id:
|
|
105
|
+
queryset = queryset.filter(examination_id=examination_id)
|
|
106
|
+
|
|
107
|
+
return queryset
|
|
108
|
+
|
|
109
|
+
@action(detail=False, methods=["get"], url_path="video/(?P<video_id>[^/.]+)")
|
|
110
|
+
def by_video(self, request, video_id=None):
|
|
111
|
+
"""
|
|
112
|
+
Get all examinations for a specific video.
|
|
113
|
+
|
|
114
|
+
**Endpoint:** GET /api/video-examinations/video/{video_id}/
|
|
115
|
+
**Alternative:** GET /api/video/{video_id}/examinations/
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
video_id: ID of the video
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
200: List of examinations for the video
|
|
122
|
+
404: Video not found
|
|
123
|
+
"""
|
|
124
|
+
# Validate video exists
|
|
125
|
+
video = get_object_or_404(VideoFile, id=video_id)
|
|
126
|
+
|
|
127
|
+
# Get examinations for this video
|
|
128
|
+
examinations = self.queryset.filter(video=video)
|
|
129
|
+
|
|
130
|
+
serializer = self.get_serializer(examinations, many=True)
|
|
131
|
+
return Response(serializer.data)
|
|
132
|
+
|
|
39
133
|
def create(self, request, *args, **kwargs):
|
|
40
134
|
"""
|
|
41
|
-
Create a new video examination
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
135
|
+
Create a new video examination.
|
|
136
|
+
|
|
137
|
+
**Endpoint:** POST /api/video-examinations/
|
|
138
|
+
|
|
139
|
+
**Payload:**
|
|
140
|
+
```json
|
|
45
141
|
{
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"locationClassificationId": 3,
|
|
51
|
-
"locationChoiceId": 4,
|
|
52
|
-
"morphologyClassificationId": 5,
|
|
53
|
-
"morphologyChoiceId": 6,
|
|
54
|
-
"interventionIds": [7, 8],
|
|
55
|
-
"notes": "Sample notes"
|
|
142
|
+
"video_id": 123,
|
|
143
|
+
"examination_id": 5,
|
|
144
|
+
"date_start": "2024-01-15",
|
|
145
|
+
"date_end": "2024-01-15"
|
|
56
146
|
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
201: Examination created successfully
|
|
151
|
+
400: Invalid data (missing required fields, validation errors)
|
|
152
|
+
404: Video or examination type not found
|
|
57
153
|
"""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
logger = logging.getLogger(__name__)
|
|
62
|
-
|
|
154
|
+
serializer = self.get_serializer(data=request.data)
|
|
155
|
+
serializer.is_valid(raise_exception=True)
|
|
156
|
+
|
|
63
157
|
try:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
# Validate data types
|
|
76
|
-
try:
|
|
77
|
-
video_id = int(data['videoId'])
|
|
78
|
-
timestamp = float(data['timestamp'])
|
|
79
|
-
examination_type_id = int(data['examinationTypeId'])
|
|
80
|
-
finding_id = int(data['findingId'])
|
|
81
|
-
except (ValueError, TypeError) as e:
|
|
82
|
-
return Response(
|
|
83
|
-
{'error': f'Invalid data type in request: {str(e)}'},
|
|
84
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
158
|
+
with transaction.atomic():
|
|
159
|
+
patient_exam = serializer.save()
|
|
160
|
+
|
|
161
|
+
# Return created examination with full serialization
|
|
162
|
+
response_serializer = VideoExaminationSerializer(patient_exam)
|
|
163
|
+
logger.info(
|
|
164
|
+
f"Created video examination: video={request.data.get('video_id')}, "
|
|
165
|
+
f"exam={request.data.get('examination_id')}"
|
|
85
166
|
)
|
|
86
|
-
|
|
87
|
-
# Validate timestamp is not negative
|
|
88
|
-
if timestamp < 0:
|
|
89
167
|
return Response(
|
|
90
|
-
|
|
91
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
168
|
+
response_serializer.data, status=status.HTTP_201_CREATED
|
|
92
169
|
)
|
|
93
|
-
|
|
94
|
-
with transaction.atomic():
|
|
95
|
-
# Get video
|
|
96
|
-
try:
|
|
97
|
-
video = VideoFile.objects.get(id=video_id)
|
|
98
|
-
except VideoFile.DoesNotExist:
|
|
99
|
-
return Response(
|
|
100
|
-
{'error': 'Video not found'},
|
|
101
|
-
status=status.HTTP_404_NOT_FOUND
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
# Get examination type
|
|
105
|
-
try:
|
|
106
|
-
examination = Examination.objects.get(id=examination_type_id)
|
|
107
|
-
except Examination.DoesNotExist:
|
|
108
|
-
return Response(
|
|
109
|
-
{'error': 'Examination type not found'},
|
|
110
|
-
status=status.HTTP_404_NOT_FOUND
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
# Get finding
|
|
114
|
-
try:
|
|
115
|
-
finding = Finding.objects.get(id=finding_id)
|
|
116
|
-
except Finding.DoesNotExist:
|
|
117
|
-
return Response(
|
|
118
|
-
{'error': 'Finding not found'},
|
|
119
|
-
status=status.HTTP_404_NOT_FOUND
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
# Validate optional foreign keys if provided
|
|
123
|
-
if data.get('locationClassificationId'):
|
|
124
|
-
try:
|
|
125
|
-
FindingClassification.objects.get(id=data['locationClassificationId'], classification_types__name__iexact="location")
|
|
126
|
-
except FindingClassification.DoesNotExist:
|
|
127
|
-
return Response(
|
|
128
|
-
{'error': 'Location classification not found'},
|
|
129
|
-
status=status.HTTP_404_NOT_FOUND
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
if data.get('morphologyClassificationId'):
|
|
133
|
-
try:
|
|
134
|
-
FindingClassification.objects.get(
|
|
135
|
-
id=data['morphologyClassificationId'],
|
|
136
|
-
classification_types__name__iexact="morphology"
|
|
137
|
-
)
|
|
138
|
-
except FindingClassification.DoesNotExist:
|
|
139
|
-
return Response(
|
|
140
|
-
{'error': 'Morphology classification not found'},
|
|
141
|
-
status=status.HTTP_404_NOT_FOUND
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
# Create examination record
|
|
145
|
-
examination_data = {
|
|
146
|
-
'id': f"exam_{video.id}_{timestamp}_{examination.id}",
|
|
147
|
-
'video_id': video_id,
|
|
148
|
-
'timestamp': timestamp,
|
|
149
|
-
'examination_type': examination.name,
|
|
150
|
-
'finding': finding.name,
|
|
151
|
-
'location_classification': data.get('locationClassificationId'),
|
|
152
|
-
'location_choice': data.get('locationChoiceId'),
|
|
153
|
-
'morphology_classification': data.get('morphologyClassificationId'),
|
|
154
|
-
'morphology_choice': data.get('morphologyChoiceId'),
|
|
155
|
-
'interventions': data.get('interventionIds', []),
|
|
156
|
-
'notes': data.get('notes', ''),
|
|
157
|
-
'created_at': '2024-01-01T00:00:00Z' # Placeholder timestamp
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
logger.info(f"Created video examination for video {video_id} at timestamp {timestamp}")
|
|
161
|
-
return Response(examination_data, status=status.HTTP_201_CREATED)
|
|
162
|
-
|
|
163
170
|
except Exception as e:
|
|
164
|
-
logger.error(f"
|
|
171
|
+
logger.error(f"Error creating video examination: {str(e)}")
|
|
165
172
|
return Response(
|
|
166
|
-
{
|
|
167
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
173
|
+
{"error": "Internal server error while creating examination"},
|
|
174
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
168
175
|
)
|
|
169
|
-
|
|
176
|
+
|
|
170
177
|
def update(self, request, *args, **kwargs):
|
|
171
178
|
"""
|
|
172
|
-
Update an existing video examination
|
|
173
|
-
|
|
179
|
+
Update an existing video examination.
|
|
180
|
+
|
|
181
|
+
**Endpoint:** PATCH /api/video-examinations/{id}/
|
|
182
|
+
|
|
183
|
+
**Payload:**
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"examination_id": 6,
|
|
187
|
+
"date_start": "2024-01-16"
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
200: Examination updated successfully
|
|
193
|
+
400: Invalid data
|
|
194
|
+
404: Examination not found
|
|
174
195
|
"""
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
196
|
+
partial = kwargs.pop("partial", False)
|
|
197
|
+
instance = self.get_object()
|
|
198
|
+
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
|
199
|
+
serializer.is_valid(raise_exception=True)
|
|
200
|
+
|
|
180
201
|
try:
|
|
181
|
-
examination_id = kwargs.get('pk')
|
|
182
|
-
if not examination_id:
|
|
183
|
-
return Response(
|
|
184
|
-
{'error': 'Examination ID is required'},
|
|
185
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
data = request.data
|
|
189
|
-
|
|
190
|
-
# Validate data types for provided fields
|
|
191
|
-
if 'videoId' in data:
|
|
192
|
-
try:
|
|
193
|
-
data['videoId'] = int(data['videoId'])
|
|
194
|
-
except (ValueError, TypeError):
|
|
195
|
-
return Response(
|
|
196
|
-
{'error': 'Invalid videoId format'},
|
|
197
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
if 'timestamp' in data:
|
|
201
|
-
try:
|
|
202
|
-
timestamp = float(data['timestamp'])
|
|
203
|
-
if timestamp < 0:
|
|
204
|
-
return Response(
|
|
205
|
-
{'error': 'Timestamp cannot be negative'},
|
|
206
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
207
|
-
)
|
|
208
|
-
data['timestamp'] = timestamp
|
|
209
|
-
except (ValueError, TypeError):
|
|
210
|
-
return Response(
|
|
211
|
-
{'error': 'Invalid timestamp format'},
|
|
212
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
213
|
-
)
|
|
214
|
-
|
|
215
202
|
with transaction.atomic():
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
{'error': 'Video not found'},
|
|
223
|
-
status=status.HTTP_404_NOT_FOUND
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
if 'examinationTypeId' in data:
|
|
227
|
-
try:
|
|
228
|
-
examination_type_id = int(data['examinationTypeId'])
|
|
229
|
-
Examination.objects.get(id=examination_type_id)
|
|
230
|
-
except (ValueError, TypeError):
|
|
231
|
-
return Response(
|
|
232
|
-
{'error': 'Invalid examination type ID format'},
|
|
233
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
234
|
-
)
|
|
235
|
-
except Examination.DoesNotExist:
|
|
236
|
-
return Response(
|
|
237
|
-
{'error': 'Examination type not found'},
|
|
238
|
-
status=status.HTTP_404_NOT_FOUND
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
if 'findingId' in data:
|
|
242
|
-
try:
|
|
243
|
-
finding_id = int(data['findingId'])
|
|
244
|
-
Finding.objects.get(id=finding_id)
|
|
245
|
-
except (ValueError, TypeError):
|
|
246
|
-
return Response(
|
|
247
|
-
{'error': 'Invalid finding ID format'},
|
|
248
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
249
|
-
)
|
|
250
|
-
except Finding.DoesNotExist:
|
|
251
|
-
return Response(
|
|
252
|
-
{'error': 'Finding not found'},
|
|
253
|
-
status=status.HTTP_404_NOT_FOUND
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
# Return updated examination data
|
|
257
|
-
examination_data = {
|
|
258
|
-
'id': examination_id,
|
|
259
|
-
'video_id': data.get('videoId'),
|
|
260
|
-
'timestamp': data.get('timestamp'),
|
|
261
|
-
'examination_type': data.get('examinationTypeId'),
|
|
262
|
-
'finding': data.get('findingId'),
|
|
263
|
-
'location_classification': data.get('locationClassificationId'),
|
|
264
|
-
'location_choice': data.get('locationChoiceId'),
|
|
265
|
-
'morphology_classification': data.get('morphologyClassificationId'),
|
|
266
|
-
'morphology_choice': data.get('morphologyChoiceId'),
|
|
267
|
-
'interventions': data.get('interventionIds', []),
|
|
268
|
-
'notes': data.get('notes', ''),
|
|
269
|
-
'updated_at': '2024-01-01T00:00:00Z' # Placeholder timestamp
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
logger.info(f"Updated video examination {examination_id}")
|
|
273
|
-
return Response(examination_data, status=status.HTTP_200_OK)
|
|
274
|
-
|
|
203
|
+
patient_exam = serializer.save()
|
|
204
|
+
|
|
205
|
+
# Return updated examination
|
|
206
|
+
response_serializer = VideoExaminationSerializer(patient_exam)
|
|
207
|
+
logger.info(f"Updated video examination {instance.id}")
|
|
208
|
+
return Response(response_serializer.data)
|
|
275
209
|
except Exception as e:
|
|
276
|
-
logger.error(f"
|
|
210
|
+
logger.error(f"Error updating video examination {instance.id}: {str(e)}")
|
|
277
211
|
return Response(
|
|
278
|
-
{
|
|
279
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
212
|
+
{"error": "Internal server error while updating examination"},
|
|
213
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
280
214
|
)
|
|
281
|
-
|
|
215
|
+
|
|
282
216
|
def destroy(self, request, *args, **kwargs):
|
|
283
217
|
"""
|
|
284
|
-
Delete a video examination
|
|
285
|
-
|
|
218
|
+
Delete a video examination.
|
|
219
|
+
|
|
220
|
+
**Endpoint:** DELETE /api/video-examinations/{id}/
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
204: Examination deleted successfully
|
|
224
|
+
404: Examination not found
|
|
286
225
|
"""
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
logger = logging.getLogger(__name__)
|
|
291
|
-
|
|
226
|
+
instance = self.get_object()
|
|
227
|
+
examination_id = instance.id
|
|
228
|
+
|
|
292
229
|
try:
|
|
293
|
-
examination_id = kwargs.get('pk')
|
|
294
|
-
if not examination_id:
|
|
295
|
-
return Response(
|
|
296
|
-
{'error': 'Examination ID is required'},
|
|
297
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
# Validate examination_id format
|
|
301
|
-
try:
|
|
302
|
-
# For now, we're using string IDs, so just validate it's not empty
|
|
303
|
-
if not str(examination_id).strip():
|
|
304
|
-
return Response(
|
|
305
|
-
{'error': 'Invalid examination ID format'},
|
|
306
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
307
|
-
)
|
|
308
|
-
except (ValueError, TypeError):
|
|
309
|
-
return Response(
|
|
310
|
-
{'error': 'Invalid examination ID format'},
|
|
311
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
312
|
-
)
|
|
313
|
-
|
|
314
230
|
with transaction.atomic():
|
|
315
|
-
|
|
316
|
-
# TODO: Implement actual examination record deletion when persistence is added
|
|
317
|
-
|
|
231
|
+
instance.delete()
|
|
318
232
|
logger.info(f"Deleted video examination {examination_id}")
|
|
319
233
|
return Response(
|
|
320
|
-
{
|
|
321
|
-
status=status.HTTP_204_NO_CONTENT
|
|
234
|
+
{"message": f"Examination {examination_id} deleted successfully"},
|
|
235
|
+
status=status.HTTP_204_NO_CONTENT,
|
|
322
236
|
)
|
|
323
|
-
|
|
324
237
|
except Exception as e:
|
|
325
|
-
logger.error(f"
|
|
238
|
+
logger.error(f"Error deleting examination {examination_id}: {str(e)}")
|
|
326
239
|
return Response(
|
|
327
|
-
{
|
|
328
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
240
|
+
{"error": "Internal server error while deleting examination"},
|
|
241
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
329
242
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: endoreg-db
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.6.5
|
|
4
4
|
Summary: EndoReg Db Django App
|
|
5
5
|
Project-URL: Homepage, https://info.coloreg.de
|
|
6
6
|
Project-URL: Repository, https://github.com/wg-lux/endoreg-db
|
|
@@ -29,7 +29,6 @@ Requires-Dist: dotenv>=0.9.9
|
|
|
29
29
|
Requires-Dist: faker>=37.6.0
|
|
30
30
|
Requires-Dist: flake8>=7.3.0
|
|
31
31
|
Requires-Dist: gunicorn>=23.0.0
|
|
32
|
-
Requires-Dist: huggingface-hub>=0.35.3
|
|
33
32
|
Requires-Dist: icecream>=2.1.4
|
|
34
33
|
Requires-Dist: librosa==0.11.0
|
|
35
34
|
Requires-Dist: llvmlite>=0.44.0
|