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

Files changed (36) hide show
  1. endoreg_db/management/commands/load_ai_model_data.py +2 -1
  2. endoreg_db/management/commands/setup_endoreg_db.py +11 -7
  3. endoreg_db/models/media/pdf/raw_pdf.py +241 -97
  4. endoreg_db/models/media/video/pipe_1.py +30 -33
  5. endoreg_db/models/media/video/video_file.py +300 -187
  6. endoreg_db/models/metadata/model_meta_logic.py +15 -1
  7. endoreg_db/models/metadata/sensitive_meta_logic.py +391 -70
  8. endoreg_db/serializers/__init__.py +26 -55
  9. endoreg_db/serializers/misc/__init__.py +1 -1
  10. endoreg_db/serializers/misc/file_overview.py +65 -35
  11. endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
  12. endoreg_db/serializers/video_examination.py +198 -0
  13. endoreg_db/services/lookup_service.py +228 -58
  14. endoreg_db/services/lookup_store.py +174 -30
  15. endoreg_db/services/pdf_import.py +585 -282
  16. endoreg_db/services/video_import.py +340 -101
  17. endoreg_db/urls/__init__.py +36 -23
  18. endoreg_db/urls/label_video_segments.py +2 -0
  19. endoreg_db/urls/media.py +3 -2
  20. endoreg_db/views/__init__.py +6 -3
  21. endoreg_db/views/media/pdf_media.py +3 -1
  22. endoreg_db/views/media/video_media.py +1 -1
  23. endoreg_db/views/media/video_segments.py +187 -259
  24. endoreg_db/views/pdf/__init__.py +5 -8
  25. endoreg_db/views/pdf/pdf_stream.py +187 -0
  26. endoreg_db/views/pdf/reimport.py +110 -94
  27. endoreg_db/views/requirement/lookup.py +171 -287
  28. endoreg_db/views/video/__init__.py +0 -2
  29. endoreg_db/views/video/video_examination_viewset.py +202 -289
  30. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.6.1.dist-info}/METADATA +1 -1
  31. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.6.1.dist-info}/RECORD +33 -34
  32. endoreg_db/views/pdf/pdf_media.py +0 -239
  33. endoreg_db/views/pdf/pdf_stream_views.py +0 -127
  34. endoreg_db/views/video/video_media.py +0 -158
  35. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.6.1.dist-info}/WHEEL +0 -0
  36. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -8,8 +8,7 @@ Provides RESTful endpoints for video segment management:
8
8
  - Video-specific: GET/POST /api/media/videos/<pk>/segments/
9
9
  """
10
10
 
11
- from endoreg_db.models import Label, LabelVideoSegment, VideoFile
12
- from endoreg_db.serializers.label_video_segment.label_video_segment import LabelVideoSegmentSerializer
11
+ import logging
13
12
 
14
13
  from django.db import transaction
15
14
  from django.db.models import Count
@@ -18,71 +17,65 @@ from rest_framework import status
18
17
  from rest_framework.decorators import api_view, permission_classes
19
18
  from rest_framework.response import Response
20
19
 
20
+ from endoreg_db.models import Label, LabelVideoSegment, VideoFile
21
+ from endoreg_db.serializers.label_video_segment.label_video_segment import LabelVideoSegmentSerializer
21
22
  from endoreg_db.utils.permissions import EnvironmentAwarePermission
22
23
 
23
- import logging
24
24
  logger = logging.getLogger(__name__)
25
25
 
26
26
 
27
- @api_view(['GET'])
27
+ @api_view(["GET"])
28
28
  @permission_classes([EnvironmentAwarePermission])
29
29
  def video_segments_stats(request):
30
30
  """
31
31
  Statistics endpoint for video segments.
32
-
32
+
33
33
  GET /api/media/videos/segments/stats/
34
34
  Returns aggregated statistics about video segments.
35
35
  """
36
36
  try:
37
37
  # Get all segments queryset
38
38
  segments = LabelVideoSegment.objects.all()
39
-
39
+
40
40
  # Calculate statistics
41
41
  total_segments = segments.count()
42
-
43
- # Segments by status (assuming status field exists)
44
- status_counts = segments.values('status').annotate(count=Count('id'))
45
-
42
+
46
43
  # Segments by label
47
- label_counts = segments.values('label__name').annotate(count=Count('id'))
48
-
44
+ label_counts = segments.values("label__name").annotate(count=Count("id"))
45
+
49
46
  # Videos with segments
50
- videos_with_segments = segments.values('video_id').distinct().count()
51
-
47
+ videos_with_segments = segments.values("video_file").distinct().count()
48
+
52
49
  stats = {
53
- 'total_segments': total_segments,
54
- 'videos_with_segments': videos_with_segments,
55
- 'by_status': {item['status']: item['count'] for item in status_counts if item['status']},
56
- 'by_label': {item['label__name']: item['count'] for item in label_counts if item['label__name']},
50
+ "total_segments": total_segments,
51
+ "videos_with_segments": videos_with_segments,
52
+ "by_label": {item["label__name"]: item["count"] for item in label_counts if item["label__name"]},
57
53
  }
58
-
54
+
59
55
  return Response(stats, status=status.HTTP_200_OK)
60
-
56
+
61
57
  except Exception as e:
62
58
  logger.error(f"Error fetching video segment stats: {e}")
63
- return Response(
64
- {'error': 'Failed to fetch segment statistics'},
65
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
66
- )
59
+ return Response({"error": "Failed to fetch segment statistics"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
67
60
 
68
61
 
69
- @api_view(['GET', 'POST'])
62
+ @api_view(["GET", "POST"])
70
63
  @permission_classes([EnvironmentAwarePermission])
71
64
  def video_segments_collection(request):
72
65
  """
73
66
  Collection endpoint for all video segments across all videos.
74
-
67
+
75
68
  GET /api/media/videos/segments/
76
69
  - Lists all segments, optionally filtered by video_id and/or label_id
77
70
  - Query params: video_id, label_id
78
-
71
+
79
72
  POST /api/media/videos/segments/
80
73
  - Creates a new video segment
81
74
  - Requires: video_id, label_id, start_frame_number, end_frame_number
82
-
75
+
83
76
  Modern replacement for: /api/video-segments/
84
77
  """
85
- if request.method == 'POST':
78
+ if request.method == "POST":
86
79
  logger.info(f"Creating new video segment with data: {request.data}")
87
80
 
88
81
  with transaction.atomic():
@@ -91,27 +84,18 @@ def video_segments_collection(request):
91
84
  try:
92
85
  segment = serializer.save()
93
86
  logger.info(f"Successfully created video segment {segment.pk}")
94
- return Response(
95
- LabelVideoSegmentSerializer(segment).data,
96
- status=status.HTTP_201_CREATED
97
- )
87
+ return Response(LabelVideoSegmentSerializer(segment).data, status=status.HTTP_201_CREATED)
98
88
  except Exception as e:
99
89
  logger.error(f"Error creating video segment: {str(e)}")
100
- return Response(
101
- {'error': f'Failed to create segment: {str(e)}'},
102
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
103
- )
90
+ return Response({"error": f"Failed to create segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
104
91
  else:
105
92
  logger.warning(f"Invalid data for video segment creation: {serializer.errors}")
106
- return Response(
107
- {'error': 'Invalid data', 'details': serializer.errors},
108
- status=status.HTTP_400_BAD_REQUEST
109
- )
93
+ return Response({"error": "Invalid data", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
110
94
 
111
- elif request.method == 'GET':
95
+ elif request.method == "GET":
112
96
  # Optional filtering by video_id
113
- video_id = request.GET.get('video_id')
114
- label_id = request.GET.get('label_id')
97
+ video_id = request.GET.get("video_id")
98
+ label_id = request.GET.get("label_id")
115
99
 
116
100
  queryset = LabelVideoSegment.objects.all()
117
101
 
@@ -120,140 +104,114 @@ def video_segments_collection(request):
120
104
  video = VideoFile.objects.get(id=video_id)
121
105
  queryset = queryset.filter(video_file=video)
122
106
  except VideoFile.DoesNotExist:
123
- return Response(
124
- {'error': f'Video with id {video_id} not found'},
125
- status=status.HTTP_404_NOT_FOUND
126
- )
107
+ return Response({"error": f"Video with id {video_id} not found"}, status=status.HTTP_404_NOT_FOUND)
127
108
 
128
109
  if label_id:
129
110
  try:
130
111
  label = Label.objects.get(id=label_id)
131
112
  queryset = queryset.filter(label=label)
132
113
  except Label.DoesNotExist:
133
- return Response(
134
- {'error': f'Label with id {label_id} not found'},
135
- status=status.HTTP_404_NOT_FOUND
136
- )
114
+ return Response({"error": f"Label with id {label_id} not found"}, status=status.HTTP_404_NOT_FOUND)
137
115
 
138
116
  # Order by video and start time for consistent results
139
- segments = queryset.order_by('video_file__id', 'start_frame_number')
117
+ segments = queryset.order_by("video_file__id", "start_frame_number")
140
118
  serializer = LabelVideoSegmentSerializer(segments, many=True)
141
119
  return Response(serializer.data)
142
120
 
143
121
 
144
- @api_view(['GET', 'POST'])
122
+ @api_view(["GET", "POST"])
145
123
  @permission_classes([EnvironmentAwarePermission])
146
124
  def video_segments_by_video(request, pk):
147
125
  """
148
126
  Video-specific segments endpoint.
149
-
127
+
150
128
  GET /api/media/videos/<pk>/segments/
151
129
  - Lists all segments for a specific video
152
130
  - Query params: label (label name filter)
153
131
  - Note: This was already implemented in segments.py as video_segments_by_pk
154
-
132
+
155
133
  POST /api/media/videos/<pk>/segments/
156
134
  - Creates a new segment for this video
157
135
  - Automatically sets video_id to pk
158
136
  - Requires: label_id, start_frame_number, end_frame_number
159
-
137
+
160
138
  Modern replacement for: /api/video-segments/?video_id=<pk>
161
139
  """
162
140
  # Verify video exists
163
141
  video = get_object_or_404(VideoFile, id=pk)
164
-
165
- if request.method == 'GET':
142
+
143
+ if request.method == "GET":
166
144
  # This duplicates video_segments_by_pk functionality
167
145
  # We keep both for compatibility during migration
168
- label_name = request.GET.get('label')
169
-
146
+ label_name = request.GET.get("label")
147
+
170
148
  queryset = LabelVideoSegment.objects.filter(video_file=video)
171
-
149
+
172
150
  if label_name:
173
151
  try:
174
152
  label = Label.objects.get(name=label_name)
175
153
  queryset = queryset.filter(label=label)
176
154
  except Label.DoesNotExist:
177
- return Response(
178
- {'error': f'Label "{label_name}" not found'},
179
- status=status.HTTP_404_NOT_FOUND
180
- )
181
-
182
- segments = queryset.order_by('start_frame_number')
155
+ return Response({"error": f'Label "{label_name}" not found'}, status=status.HTTP_404_NOT_FOUND)
156
+
157
+ segments = queryset.order_by("start_frame_number")
183
158
  serializer = LabelVideoSegmentSerializer(segments, many=True)
184
159
  return Response(serializer.data)
185
-
186
- elif request.method == 'POST':
160
+
161
+ elif request.method == "POST":
187
162
  logger.info(f"Creating new segment for video {pk} with data: {request.data}")
188
-
163
+
189
164
  # Automatically set video_id to pk
190
165
  data = request.data.copy()
191
- data['video_id'] = pk
192
-
166
+ data["video_id"] = pk
167
+
193
168
  with transaction.atomic():
194
169
  serializer = LabelVideoSegmentSerializer(data=data)
195
170
  if serializer.is_valid():
196
171
  try:
197
172
  segment = serializer.save()
198
173
  logger.info(f"Successfully created segment {segment.pk} for video {pk}")
199
- return Response(
200
- LabelVideoSegmentSerializer(segment).data,
201
- status=status.HTTP_201_CREATED
202
- )
174
+ return Response(LabelVideoSegmentSerializer(segment).data, status=status.HTTP_201_CREATED)
203
175
  except Exception as e:
204
176
  logger.error(f"Error creating segment for video {pk}: {str(e)}")
205
- return Response(
206
- {'error': f'Failed to create segment: {str(e)}'},
207
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
208
- )
177
+ return Response({"error": f"Failed to create segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
209
178
  else:
210
179
  logger.warning(f"Invalid data for segment creation: {serializer.errors}")
211
- return Response(
212
- {'error': 'Invalid data', 'details': serializer.errors},
213
- status=status.HTTP_400_BAD_REQUEST
214
- )
180
+ return Response({"error": "Invalid data", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
215
181
 
216
182
 
217
- @api_view(['GET', 'PATCH', 'DELETE'])
183
+ @api_view(["GET", "PATCH", "DELETE"])
218
184
  @permission_classes([EnvironmentAwarePermission])
219
185
  def video_segment_detail(request, pk, segment_id):
220
186
  """
221
187
  Detail endpoint for a specific video segment.
222
-
188
+
223
189
  GET /api/media/videos/<pk>/segments/<segment_id>/
224
190
  - Returns segment details
225
-
191
+
226
192
  PATCH /api/media/videos/<pk>/segments/<segment_id>/
227
193
  - Updates segment (partial update)
228
-
194
+
229
195
  DELETE /api/media/videos/<pk>/segments/<segment_id>/
230
196
  - Deletes segment
231
-
197
+
232
198
  Modern replacement for: /api/video-segments/<segment_id>/
233
199
  """
234
200
  # Verify video exists
235
201
  video = get_object_or_404(VideoFile, id=pk)
236
-
202
+
237
203
  # Get segment and verify it belongs to this video
238
- segment = get_object_or_404(
239
- LabelVideoSegment,
240
- id=segment_id,
241
- video_file=video
242
- )
243
-
244
- if request.method == 'GET':
204
+ segment = get_object_or_404(LabelVideoSegment, id=segment_id, video_file=video)
205
+
206
+ if request.method == "GET":
245
207
  serializer = LabelVideoSegmentSerializer(segment)
246
208
  return Response(serializer.data)
247
-
248
- elif request.method == 'PATCH':
209
+
210
+ elif request.method == "PATCH":
249
211
  logger.info(f"Updating segment {segment_id} for video {pk} with data: {request.data}")
250
-
212
+
251
213
  with transaction.atomic():
252
- serializer = LabelVideoSegmentSerializer(
253
- segment,
254
- data=request.data,
255
- partial=True
256
- )
214
+ serializer = LabelVideoSegmentSerializer(segment, data=request.data, partial=True)
257
215
  if serializer.is_valid():
258
216
  try:
259
217
  segment = serializer.save()
@@ -261,33 +219,21 @@ def video_segment_detail(request, pk, segment_id):
261
219
  return Response(LabelVideoSegmentSerializer(segment).data)
262
220
  except Exception as e:
263
221
  logger.error(f"Error updating segment {segment_id}: {str(e)}")
264
- return Response(
265
- {'error': f'Failed to update segment: {str(e)}'},
266
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
267
- )
222
+ return Response({"error": f"Failed to update segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
268
223
  else:
269
224
  logger.warning(f"Invalid data for segment update: {serializer.errors}")
270
- return Response(
271
- {'error': 'Invalid data', 'details': serializer.errors},
272
- status=status.HTTP_400_BAD_REQUEST
273
- )
274
-
275
- elif request.method == 'DELETE':
225
+ return Response({"error": "Invalid data", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
226
+
227
+ elif request.method == "DELETE":
276
228
  logger.info(f"Deleting segment {segment_id} from video {pk}")
277
229
  try:
278
230
  with transaction.atomic():
279
231
  segment.delete()
280
232
  logger.info(f"Successfully deleted segment {segment_id}")
281
- return Response(
282
- {'message': f'Segment {segment_id} deleted successfully'},
283
- status=status.HTTP_204_NO_CONTENT
284
- )
233
+ return Response({"message": f"Segment {segment_id} deleted successfully"}, status=status.HTTP_204_NO_CONTENT)
285
234
  except Exception as e:
286
235
  logger.error(f"Error deleting segment {segment_id}: {str(e)}")
287
- return Response(
288
- {'error': f'Failed to delete segment: {str(e)}'},
289
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
290
- )
236
+ return Response({"error": f"Failed to delete segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
291
237
 
292
238
 
293
239
  # ============================================================================
@@ -295,23 +241,24 @@ def video_segment_detail(request, pk, segment_id):
295
241
  # Migrated from /api/label-video-segment/*/validate/ (October 14, 2025)
296
242
  # ============================================================================
297
243
 
298
- @api_view(['POST'])
244
+
245
+ @api_view(["POST"])
299
246
  @permission_classes([EnvironmentAwarePermission])
300
247
  def video_segment_validate(request, pk: int, segment_id: int):
301
248
  """
302
249
  Validate a single video segment.
303
-
250
+
304
251
  POST /api/media/videos/<pk>/segments/<segment_id>/validate/
305
-
252
+
306
253
  Validates a single LabelVideoSegment and marks it as verified.
307
254
  Used to confirm user-reviewed segment annotations.
308
-
255
+
309
256
  Request Body (optional):
310
257
  {
311
258
  "is_validated": true, // optional, default true
312
259
  "notes": "..." // optional, validation notes
313
260
  }
314
-
261
+
315
262
  Response:
316
263
  {
317
264
  "message": "Segment validated successfully",
@@ -325,69 +272,64 @@ def video_segment_validate(request, pk: int, segment_id: int):
325
272
  """
326
273
  # Verify video exists
327
274
  video = get_object_or_404(VideoFile, pk=pk)
328
-
275
+
329
276
  # Get segment and verify it belongs to this video
330
- segment = get_object_or_404(
331
- LabelVideoSegment.objects.select_related('state', 'video_file', 'label'),
332
- pk=segment_id,
333
- video_file=video
334
- )
335
-
277
+ segment = get_object_or_404(LabelVideoSegment.objects.select_related("state", "video_file", "label"), pk=segment_id, video_file=video)
278
+
336
279
  try:
337
280
  # Validation status from request (default: True)
338
- is_validated = request.data.get('is_validated', True)
339
- notes = request.data.get('notes', '')
340
-
281
+ is_validated = request.data.get("is_validated", True)
282
+ notes = request.data.get("notes", "")
283
+
341
284
  # Get or create state object
342
- if not hasattr(segment, 'state') or segment.state is None:
343
- return Response({
344
- "error": "Segment has no state object. Cannot validate."
345
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
346
-
285
+ if not hasattr(segment, "state") or segment.state is None:
286
+ return Response({"error": "Segment has no state object. Cannot validate."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
287
+
347
288
  # Update state
348
289
  with transaction.atomic():
349
290
  segment.state.is_validated = is_validated
350
- if notes and hasattr(segment.state, 'validation_notes'):
291
+ if notes and hasattr(segment.state, "validation_notes"):
351
292
  segment.state.validation_notes = notes
352
293
  segment.state.save()
353
-
294
+
354
295
  logger.info(f"Validated segment {segment_id} in video {pk}: {is_validated}")
355
-
356
- return Response({
357
- "message": f"Segment {segment_id} validation status updated",
358
- "segment_id": segment_id,
359
- "is_validated": is_validated,
360
- "label": segment.label.name if segment.label else None,
361
- "video_id": video.id,
362
- "start_frame": segment.start_frame_number,
363
- "end_frame": segment.end_frame_number
364
- }, status=status.HTTP_200_OK)
365
-
296
+
297
+ return Response(
298
+ {
299
+ "message": f"Segment {segment_id} validation status updated",
300
+ "segment_id": segment_id,
301
+ "is_validated": is_validated,
302
+ "label": segment.label.name if segment.label else None,
303
+ "video_id": video.id,
304
+ "start_frame": segment.start_frame_number,
305
+ "end_frame": segment.end_frame_number,
306
+ },
307
+ status=status.HTTP_200_OK,
308
+ )
309
+
366
310
  except Exception as e:
367
311
  logger.error(f"Error validating segment {segment_id} in video {pk}: {e}")
368
- return Response({
369
- "error": f"Validation failed: {str(e)}"
370
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
312
+ return Response({"error": f"Validation failed: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
371
313
 
372
314
 
373
- @api_view(['POST'])
315
+ @api_view(["POST"])
374
316
  @permission_classes([EnvironmentAwarePermission])
375
317
  def video_segments_validate_bulk(request, pk: int):
376
318
  """
377
319
  Validate multiple video segments at once.
378
-
320
+
379
321
  POST /api/media/videos/<pk>/segments/validate-bulk/
380
-
322
+
381
323
  Validates multiple LabelVideoSegments simultaneously.
382
324
  Useful for batch validation after review.
383
-
325
+
384
326
  Request Body:
385
327
  {
386
328
  "segment_ids": [1, 2, 3, ...],
387
329
  "is_validated": true, // optional, default true
388
330
  "notes": "..." // optional, applies to all segments
389
331
  }
390
-
332
+
391
333
  Response:
392
334
  {
393
335
  "message": "Bulk validation completed. 3 segments updated.",
@@ -399,37 +341,30 @@ def video_segments_validate_bulk(request, pk: int):
399
341
  """
400
342
  # Verify video exists
401
343
  video = get_object_or_404(VideoFile, pk=pk)
402
-
403
- segment_ids = request.data.get('segment_ids', [])
404
- is_validated = request.data.get('is_validated', True)
405
- notes = request.data.get('notes', '')
406
-
344
+
345
+ segment_ids = request.data.get("segment_ids", [])
346
+ is_validated = request.data.get("is_validated", True)
347
+ notes = request.data.get("notes", "")
348
+
407
349
  if not segment_ids:
408
- return Response({
409
- "error": "segment_ids is required"
410
- }, status=status.HTTP_400_BAD_REQUEST)
411
-
350
+ return Response({"error": "segment_ids is required"}, status=status.HTTP_400_BAD_REQUEST)
351
+
412
352
  try:
413
353
  # Get all segments for this video only
414
- segments = LabelVideoSegment.objects.filter(
415
- pk__in=segment_ids,
416
- video_file=video
417
- ).select_related('state')
418
-
354
+ segments = LabelVideoSegment.objects.filter(pk__in=segment_ids, video_file=video).select_related("state")
355
+
419
356
  if not segments.exists():
420
- return Response({
421
- "error": "No segments found with provided IDs for this video"
422
- }, status=status.HTTP_404_NOT_FOUND)
423
-
357
+ return Response({"error": "No segments found with provided IDs for this video"}, status=status.HTTP_404_NOT_FOUND)
358
+
424
359
  updated_count = 0
425
360
  failed_ids = []
426
-
361
+
427
362
  with transaction.atomic():
428
363
  for segment in segments:
429
364
  try:
430
365
  if segment.state:
431
366
  segment.state.is_validated = is_validated
432
- if notes and hasattr(segment.state, 'validation_notes'):
367
+ if notes and hasattr(segment.state, "validation_notes"):
433
368
  segment.state.validation_notes = notes
434
369
  segment.state.save()
435
370
  updated_count += 1
@@ -438,51 +373,49 @@ def video_segments_validate_bulk(request, pk: int):
438
373
  except Exception as e:
439
374
  logger.error(f"Error validating segment {segment.id}: {e}")
440
375
  failed_ids.append(segment.id)
441
-
376
+
442
377
  logger.info(f"Bulk validated {updated_count} segments in video {pk}")
443
-
378
+
444
379
  response_data = {
445
380
  "message": f"Bulk validation completed. {updated_count} segments updated.",
446
381
  "updated_count": updated_count,
447
382
  "requested_count": len(segment_ids),
448
383
  "is_validated": is_validated,
449
- "video_id": pk
384
+ "video_id": pk,
450
385
  }
451
-
386
+
452
387
  if failed_ids:
453
388
  response_data["failed_ids"] = failed_ids
454
389
  response_data["warning"] = f"{len(failed_ids)} segments could not be validated"
455
-
390
+
456
391
  return Response(response_data, status=status.HTTP_200_OK)
457
-
392
+
458
393
  except Exception as e:
459
394
  logger.error(f"Error in bulk validation for video {pk}: {e}")
460
- return Response({
461
- "error": f"Bulk validation failed: {str(e)}"
462
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
395
+ return Response({"error": f"Bulk validation failed: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
463
396
 
464
397
 
465
- @api_view(['GET', 'POST'])
398
+ @api_view(["GET", "POST"])
466
399
  @permission_classes([EnvironmentAwarePermission])
467
400
  def video_segments_validation_status(request, pk: int):
468
401
  """
469
402
  Get or update validation status for all segments of a video.
470
-
403
+
471
404
  GET /api/media/videos/<pk>/segments/validation-status/
472
405
  Returns validation statistics for all segments.
473
-
406
+
474
407
  POST /api/media/videos/<pk>/segments/validation-status/
475
408
  Marks all segments (or filtered by label) as validated.
476
-
409
+
477
410
  Query Parameters (GET):
478
411
  - label_name: filter by label (optional)
479
-
412
+
480
413
  Request Body (POST, optional):
481
414
  {
482
415
  "label_name": "...", // optional, only validate segments with this label
483
416
  "notes": "..." // optional
484
417
  }
485
-
418
+
486
419
  Response (GET):
487
420
  {
488
421
  "video_id": 123,
@@ -492,7 +425,7 @@ def video_segments_validation_status(request, pk: int):
492
425
  "validation_complete": false,
493
426
  "by_label": {...}
494
427
  }
495
-
428
+
496
429
  Response (POST):
497
430
  {
498
431
  "message": "Video segment validation completed",
@@ -504,77 +437,69 @@ def video_segments_validation_status(request, pk: int):
504
437
  """
505
438
  # Verify video exists
506
439
  video = get_object_or_404(VideoFile, pk=pk)
507
-
508
- if request.method == 'GET':
440
+
441
+ if request.method == "GET":
509
442
  # Get validation status
510
- label_name = request.query_params.get('label_name')
511
-
512
- segments_query = LabelVideoSegment.objects.filter(
513
- video_file=video
514
- ).select_related('state', 'label')
515
-
443
+ label_name = request.query_params.get("label_name")
444
+
445
+ segments_query = LabelVideoSegment.objects.filter(video_file=video).select_related("state", "label")
446
+
516
447
  if label_name:
517
448
  segments_query = segments_query.filter(label__name=label_name)
518
-
449
+
519
450
  segments = segments_query.all()
520
451
  total_count = segments.count()
521
-
452
+
522
453
  # Count validated segments
523
- validated_count = sum(
524
- 1 for s in segments
525
- if s.state and s.state.is_validated
526
- )
527
-
454
+ validated_count = sum(1 for s in segments if s.state and s.state.is_validated)
455
+
528
456
  # By label breakdown
529
457
  by_label = {}
530
458
  for segment in segments:
531
- label = segment.label.name if segment.label else 'unknown'
459
+ label = segment.label.name if segment.label else "unknown"
532
460
  if label not in by_label:
533
- by_label[label] = {'total': 0, 'validated': 0}
534
- by_label[label]['total'] += 1
461
+ by_label[label] = {"total": 0, "validated": 0}
462
+ by_label[label]["total"] += 1
535
463
  if segment.state and segment.state.is_validated:
536
- by_label[label]['validated'] += 1
537
-
538
- return Response({
539
- "video_id": pk,
540
- "total_segments": total_count,
541
- "validated_count": validated_count,
542
- "unvalidated_count": total_count - validated_count,
543
- "validation_complete": validated_count == total_count and total_count > 0,
544
- "by_label": by_label,
545
- "label_filter": label_name
546
- }, status=status.HTTP_200_OK)
547
-
548
- elif request.method == 'POST':
464
+ by_label[label]["validated"] += 1
465
+
466
+ return Response(
467
+ {
468
+ "video_id": pk,
469
+ "total_segments": total_count,
470
+ "validated_count": validated_count,
471
+ "unvalidated_count": total_count - validated_count,
472
+ "validation_complete": validated_count == total_count and total_count > 0,
473
+ "by_label": by_label,
474
+ "label_filter": label_name,
475
+ },
476
+ status=status.HTTP_200_OK,
477
+ )
478
+
479
+ elif request.method == "POST":
549
480
  # Mark all segments as validated
550
- label_name = request.data.get('label_name')
551
- notes = request.data.get('notes', '')
552
-
553
- segments_query = LabelVideoSegment.objects.filter(
554
- video_file=video
555
- ).select_related('state', 'label')
556
-
481
+ label_name = request.data.get("label_name")
482
+ notes = request.data.get("notes", "")
483
+
484
+ segments_query = LabelVideoSegment.objects.filter(video_file=video).select_related("state", "label")
485
+
557
486
  if label_name:
558
487
  segments_query = segments_query.filter(label__name=label_name)
559
-
488
+
560
489
  segments = segments_query.all()
561
-
490
+
562
491
  if not segments.exists():
563
- return Response({
564
- "message": "No segments found to validate",
565
- "video_id": pk,
566
- "updated_count": 0
567
- }, status=status.HTTP_200_OK)
568
-
492
+ return Response({"message": "No segments found to validate", "video_id": pk, "updated_count": 0}, status=status.HTTP_200_OK)
493
+
569
494
  updated_count = 0
570
495
  failed_count = 0
571
-
496
+
572
497
  with transaction.atomic():
573
498
  for segment in segments:
574
499
  try:
575
500
  if segment.state:
576
501
  segment.state.is_validated = True
577
- if notes and hasattr(segment.state, 'validation_notes'):
502
+ if notes and hasattr(segment.state, "validation_notes"):
578
503
  segment.state.validation_notes = notes
579
504
  segment.state.save()
580
505
  updated_count += 1
@@ -583,14 +508,17 @@ def video_segments_validation_status(request, pk: int):
583
508
  except Exception as e:
584
509
  logger.error(f"Error validating segment {segment.id}: {e}")
585
510
  failed_count += 1
586
-
511
+
587
512
  logger.info(f"Completed validation for {updated_count} segments in video {pk}")
588
-
589
- return Response({
590
- "message": f"Video segment validation completed for video {pk}",
591
- "video_id": pk,
592
- "total_segments": len(segments),
593
- "updated_count": updated_count,
594
- "failed_count": failed_count,
595
- "label_filter": label_name
596
- }, status=status.HTTP_200_OK)
513
+
514
+ return Response(
515
+ {
516
+ "message": f"Video segment validation completed for video {pk}",
517
+ "video_id": pk,
518
+ "total_segments": len(segments),
519
+ "updated_count": updated_count,
520
+ "failed_count": failed_count,
521
+ "label_filter": label_name,
522
+ },
523
+ status=status.HTTP_200_OK,
524
+ )