endoreg-db 0.8.5.3__py3-none-any.whl → 0.8.5.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/models/media/pdf/raw_pdf.py +241 -97
- endoreg_db/models/media/video/video_file.py +23 -5
- endoreg_db/models/metadata/sensitive_meta_logic.py +30 -5
- endoreg_db/serializers/__init__.py +26 -55
- endoreg_db/serializers/video_examination.py +198 -0
- endoreg_db/urls/__init__.py +36 -23
- endoreg_db/views/pdf/reimport.py +110 -94
- endoreg_db/views/video/video_examination_viewset.py +202 -289
- {endoreg_db-0.8.5.3.dist-info → endoreg_db-0.8.5.5.dist-info}/METADATA +1 -1
- {endoreg_db-0.8.5.3.dist-info → endoreg_db-0.8.5.5.dist-info}/RECORD +12 -11
- {endoreg_db-0.8.5.3.dist-info → endoreg_db-0.8.5.5.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.5.3.dist-info → endoreg_db-0.8.5.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -523,8 +523,13 @@ class VideoFile(models.Model):
|
|
|
523
523
|
"""
|
|
524
524
|
Validate the metadata of the VideoFile instance.
|
|
525
525
|
|
|
526
|
-
Called after annotation in the frontend, this method
|
|
527
|
-
|
|
526
|
+
Called after annotation in the frontend, this method:
|
|
527
|
+
1. Updates sensitive metadata with user-annotated data
|
|
528
|
+
2. Deletes the RAW video file (keeping only the anonymized version)
|
|
529
|
+
3. Marks the video as validated
|
|
530
|
+
|
|
531
|
+
**IMPORTANT:** Only the raw video is deleted. The processed (anonymized)
|
|
532
|
+
video is preserved as the final validated output.
|
|
528
533
|
"""
|
|
529
534
|
from datetime import date as dt_date
|
|
530
535
|
|
|
@@ -541,9 +546,22 @@ class VideoFile(models.Model):
|
|
|
541
546
|
}
|
|
542
547
|
self.sensitive_meta = SensitiveMeta.create_from_dict(default_data)
|
|
543
548
|
|
|
544
|
-
# Delete
|
|
545
|
-
|
|
546
|
-
|
|
549
|
+
# CRITICAL FIX: Delete RAW video file, not the processed (anonymized) one
|
|
550
|
+
# After validation, only the anonymized video should remain
|
|
551
|
+
from .video_file_io import _get_raw_file_path
|
|
552
|
+
|
|
553
|
+
raw_path = _get_raw_file_path(self)
|
|
554
|
+
if raw_path and raw_path.exists():
|
|
555
|
+
logger.info(f"Deleting raw video file after validation: {raw_path}")
|
|
556
|
+
raw_path.unlink(missing_ok=True)
|
|
557
|
+
# Clear the raw_file field in database (use delete() to avoid save issues)
|
|
558
|
+
if self.raw_file:
|
|
559
|
+
self.raw_file.delete(save=False)
|
|
560
|
+
logger.info(
|
|
561
|
+
f"Raw video deleted for {self.uuid}. Anonymized video preserved."
|
|
562
|
+
)
|
|
563
|
+
else:
|
|
564
|
+
logger.warning(f"Raw video file not found for deletion: {self.uuid}")
|
|
547
565
|
|
|
548
566
|
# Update sensitive metadata with user annotations
|
|
549
567
|
sensitive_meta = _update_text_metadata(
|
|
@@ -765,17 +765,37 @@ def update_sensitive_meta_from_dict(
|
|
|
765
765
|
k: v for k, v in data.items() if k in field_names and k not in excluded_fields
|
|
766
766
|
}
|
|
767
767
|
|
|
768
|
-
# Handle potential Center update
|
|
768
|
+
# Handle potential Center update - accept both center_name (string) and center (object)
|
|
769
|
+
from ..administration import Center
|
|
770
|
+
|
|
771
|
+
center = data.get("center") # First try direct Center object
|
|
769
772
|
center_name = data.get("center_name")
|
|
770
|
-
|
|
773
|
+
|
|
774
|
+
if center is not None:
|
|
775
|
+
# Center object provided directly - validate and update
|
|
776
|
+
if isinstance(center, Center):
|
|
777
|
+
instance.center = center
|
|
778
|
+
logger.debug(f"Updated center from Center object: {center.name}")
|
|
779
|
+
else:
|
|
780
|
+
logger.warning(
|
|
781
|
+
f"Invalid center type {type(center)}, expected Center instance. Ignoring."
|
|
782
|
+
)
|
|
783
|
+
# Remove from selected_data to prevent override
|
|
784
|
+
selected_data.pop("center", None)
|
|
785
|
+
elif center_name:
|
|
786
|
+
# center_name string provided - resolve to Center object
|
|
771
787
|
try:
|
|
772
|
-
|
|
773
|
-
instance.center =
|
|
788
|
+
center_obj = Center.objects.get(name=center_name)
|
|
789
|
+
instance.center = center_obj
|
|
790
|
+
logger.debug(f"Updated center from center_name string: {center_name}")
|
|
774
791
|
except Center.DoesNotExist:
|
|
775
792
|
logger.warning(
|
|
776
793
|
f"Center '{center_name}' not found during update. Keeping existing center."
|
|
777
794
|
)
|
|
778
|
-
|
|
795
|
+
else:
|
|
796
|
+
# Both are None/missing - remove 'center' from selected_data to preserve existing value
|
|
797
|
+
selected_data.pop("center", None)
|
|
798
|
+
# If both are None/missing, keep existing center (no update needed)
|
|
779
799
|
|
|
780
800
|
# Set examiner names if provided, before calling save
|
|
781
801
|
examiner_first_name = data.get("examiner_first_name")
|
|
@@ -858,6 +878,11 @@ def update_sensitive_meta_from_dict(
|
|
|
858
878
|
# Update other attributes from selected_data
|
|
859
879
|
patient_name_changed = False
|
|
860
880
|
for k, v in selected_data.items():
|
|
881
|
+
# Skip None values to avoid overwriting existing data
|
|
882
|
+
if v is None:
|
|
883
|
+
logger.debug(f"Skipping field '{k}' during update because value is None")
|
|
884
|
+
continue
|
|
885
|
+
|
|
861
886
|
# Avoid overwriting examiner names if they were just explicitly set
|
|
862
887
|
if (
|
|
863
888
|
k not in ["examiner_first_name", "examiner_last_name"]
|
|
@@ -1,81 +1,60 @@
|
|
|
1
1
|
from .administration import (
|
|
2
|
+
ActiveModelSerializer,
|
|
3
|
+
AiModelSerializer,
|
|
2
4
|
CenterSerializer,
|
|
3
5
|
GenderSerializer,
|
|
4
|
-
ActiveModelSerializer,
|
|
5
6
|
ModelTypeSerializer,
|
|
6
|
-
AiModelSerializer
|
|
7
7
|
)
|
|
8
|
-
|
|
9
8
|
from .examination import (
|
|
9
|
+
ExaminationDropdownSerializer,
|
|
10
10
|
ExaminationSerializer,
|
|
11
11
|
ExaminationTypeSerializer,
|
|
12
|
-
ExaminationDropdownSerializer
|
|
13
12
|
)
|
|
14
|
-
|
|
15
13
|
from .finding import FindingSerializer
|
|
16
|
-
|
|
17
14
|
from .finding_classification import (
|
|
18
15
|
FindingClassificationChoiceSerializer,
|
|
19
|
-
FindingClassificationSerializer
|
|
16
|
+
FindingClassificationSerializer,
|
|
20
17
|
)
|
|
21
|
-
|
|
22
|
-
from .label import (
|
|
23
|
-
LabelSerializer,
|
|
24
|
-
ImageClassificationAnnotationSerializer
|
|
25
|
-
)
|
|
26
|
-
|
|
18
|
+
from .label import ImageClassificationAnnotationSerializer, LabelSerializer
|
|
27
19
|
from .label_video_segment import (
|
|
28
|
-
LabelVideoSegmentSerializer,
|
|
29
20
|
LabelVideoSegmentAnnotationSerializer,
|
|
21
|
+
LabelVideoSegmentSerializer,
|
|
30
22
|
)
|
|
31
|
-
|
|
32
23
|
from .meta import (
|
|
33
|
-
ReportMetaSerializer,
|
|
34
24
|
PDFFileForMetaSerializer,
|
|
25
|
+
ReportMetaSerializer,
|
|
35
26
|
SensitiveMetaDetailSerializer,
|
|
36
27
|
SensitiveMetaUpdateSerializer,
|
|
37
28
|
SensitiveMetaVerificationSerializer,
|
|
38
29
|
VideoMetaSerializer,
|
|
39
30
|
)
|
|
40
|
-
|
|
41
31
|
from .misc import (
|
|
42
32
|
FileOverviewSerializer,
|
|
43
|
-
VoPPatientDataSerializer,
|
|
44
33
|
StatsSerializer,
|
|
45
|
-
|
|
34
|
+
TranslatableFieldMixin,
|
|
46
35
|
UploadCreateResponseSerializer,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
from .patient import (
|
|
51
|
-
PatientSerializer,
|
|
52
|
-
PatientDropdownSerializer,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
from .patient_examination import (
|
|
56
|
-
PatientExaminationSerializer,
|
|
36
|
+
UploadJobStatusSerializer,
|
|
37
|
+
VoPPatientDataSerializer,
|
|
57
38
|
)
|
|
58
|
-
|
|
39
|
+
from .patient import PatientDropdownSerializer, PatientSerializer
|
|
40
|
+
from .patient_examination import PatientExaminationSerializer
|
|
59
41
|
from .patient_finding import (
|
|
60
|
-
PatientFindingSerializer,
|
|
61
42
|
PatientFindingClassificationSerializer,
|
|
62
43
|
PatientFindingDetailSerializer,
|
|
63
44
|
PatientFindingInterventionSerializer,
|
|
64
45
|
PatientFindingListSerializer,
|
|
46
|
+
PatientFindingSerializer,
|
|
65
47
|
PatientFindingWriteSerializer,
|
|
66
48
|
)
|
|
67
|
-
|
|
68
|
-
from .
|
|
69
|
-
RawPdfAnonyTextSerializer
|
|
70
|
-
)
|
|
71
|
-
from .report import (
|
|
72
|
-
ReportListSerializer,
|
|
73
|
-
ReportDataSerializer,
|
|
74
|
-
SecureFileUrlSerializer
|
|
75
|
-
)
|
|
76
|
-
|
|
49
|
+
from .pdf import RawPdfAnonyTextSerializer
|
|
50
|
+
from .report import ReportDataSerializer, ReportListSerializer, SecureFileUrlSerializer
|
|
77
51
|
from .video.video_metadata import VideoMetadataSerializer
|
|
78
52
|
from .video.video_processing_history import VideoProcessingHistorySerializer
|
|
53
|
+
from .video_examination import (
|
|
54
|
+
VideoExaminationCreateSerializer,
|
|
55
|
+
VideoExaminationSerializer,
|
|
56
|
+
VideoExaminationUpdateSerializer,
|
|
57
|
+
)
|
|
79
58
|
|
|
80
59
|
__all__ = [
|
|
81
60
|
# Administration
|
|
@@ -84,24 +63,19 @@ __all__ = [
|
|
|
84
63
|
"ActiveModelSerializer",
|
|
85
64
|
"ModelTypeSerializer",
|
|
86
65
|
"AiModelSerializer",
|
|
87
|
-
|
|
88
66
|
# Examination
|
|
89
67
|
"ExaminationSerializer",
|
|
90
68
|
"ExaminationTypeSerializer",
|
|
91
69
|
"ExaminationDropdownSerializer",
|
|
92
|
-
|
|
93
70
|
# Finding
|
|
94
|
-
|
|
95
|
-
|
|
71
|
+
"FindingSerializer",
|
|
72
|
+
"FindingClassificationSerializer",
|
|
96
73
|
"FindingClassificationChoiceSerializer",
|
|
97
|
-
|
|
98
74
|
"LabelSerializer",
|
|
99
75
|
"ImageClassificationAnnotationSerializer",
|
|
100
|
-
|
|
101
76
|
# LabelVideoSegment
|
|
102
77
|
"LabelVideoSegmentSerializer",
|
|
103
78
|
"LabelVideoSegmentAnnotationSerializer",
|
|
104
|
-
|
|
105
79
|
# Meta
|
|
106
80
|
"PDFFileForMetaSerializer",
|
|
107
81
|
"ReportMetaSerializer",
|
|
@@ -109,7 +83,6 @@ __all__ = [
|
|
|
109
83
|
"SensitiveMetaUpdateSerializer",
|
|
110
84
|
"SensitiveMetaVerificationSerializer",
|
|
111
85
|
"VideoMetaSerializer",
|
|
112
|
-
|
|
113
86
|
# Misc
|
|
114
87
|
"FileOverviewSerializer",
|
|
115
88
|
"VoPPatientDataSerializer",
|
|
@@ -117,14 +90,11 @@ __all__ = [
|
|
|
117
90
|
"UploadJobStatusSerializer",
|
|
118
91
|
"UploadCreateResponseSerializer",
|
|
119
92
|
"TranslatableFieldMixin",
|
|
120
|
-
|
|
121
93
|
# Patient
|
|
122
94
|
"PatientSerializer",
|
|
123
95
|
"PatientDropdownSerializer",
|
|
124
|
-
|
|
125
96
|
# Patient Examination
|
|
126
97
|
"PatientExaminationSerializer",
|
|
127
|
-
|
|
128
98
|
# Patient Finding
|
|
129
99
|
"PatientFindingSerializer",
|
|
130
100
|
"PatientFindingClassificationSerializer",
|
|
@@ -132,16 +102,17 @@ __all__ = [
|
|
|
132
102
|
"PatientFindingInterventionSerializer",
|
|
133
103
|
"PatientFindingListSerializer",
|
|
134
104
|
"PatientFindingWriteSerializer",
|
|
135
|
-
|
|
136
105
|
# PDF
|
|
137
106
|
"RawPdfAnonyTextSerializer",
|
|
138
|
-
|
|
139
107
|
# Report
|
|
140
108
|
"ReportListSerializer",
|
|
141
109
|
"ReportDataSerializer",
|
|
142
110
|
"SecureFileUrlSerializer",
|
|
143
|
-
|
|
144
111
|
# Video Correction (Phase 1.1)
|
|
145
112
|
"VideoMetadataSerializer",
|
|
146
113
|
"VideoProcessingHistorySerializer",
|
|
114
|
+
# Video Examination
|
|
115
|
+
"VideoExaminationSerializer",
|
|
116
|
+
"VideoExaminationCreateSerializer",
|
|
117
|
+
"VideoExaminationUpdateSerializer",
|
|
147
118
|
]
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Video Examination Serializer
|
|
3
|
+
|
|
4
|
+
Serializes PatientExamination instances that are associated with VideoFile records.
|
|
5
|
+
This allows frontend components like VideoExaminationAnnotation.vue to display
|
|
6
|
+
and manage examinations within the video annotation workflow.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from rest_framework import serializers
|
|
10
|
+
|
|
11
|
+
from ..models import Examination, PatientExamination, VideoFile
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VideoExaminationSerializer(serializers.ModelSerializer):
|
|
15
|
+
"""
|
|
16
|
+
Serializer for video-based patient examinations.
|
|
17
|
+
|
|
18
|
+
Exposes examination data within the context of video annotation:
|
|
19
|
+
- Basic examination metadata (type, date, hash)
|
|
20
|
+
- Related patient information (anonymized)
|
|
21
|
+
- Video reference
|
|
22
|
+
- Associated findings
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# Custom fields for frontend compatibility
|
|
26
|
+
examination_name = serializers.CharField(source="examination.name", read_only=True)
|
|
27
|
+
examination_id = serializers.IntegerField(source="examination.id", read_only=True)
|
|
28
|
+
video_id = serializers.IntegerField(source="video.id", read_only=True)
|
|
29
|
+
patient_hash = serializers.CharField(source="patient.patient_hash", read_only=True)
|
|
30
|
+
|
|
31
|
+
# Nested findings data
|
|
32
|
+
findings = serializers.SerializerMethodField()
|
|
33
|
+
|
|
34
|
+
class Meta:
|
|
35
|
+
model = PatientExamination
|
|
36
|
+
fields = [
|
|
37
|
+
"id",
|
|
38
|
+
"hash",
|
|
39
|
+
"examination_id",
|
|
40
|
+
"examination_name",
|
|
41
|
+
"video_id",
|
|
42
|
+
"patient_hash",
|
|
43
|
+
"date_start",
|
|
44
|
+
"date_end",
|
|
45
|
+
"findings",
|
|
46
|
+
]
|
|
47
|
+
read_only_fields = ["hash", "patient_hash"]
|
|
48
|
+
|
|
49
|
+
def get_findings(self, obj):
|
|
50
|
+
"""
|
|
51
|
+
Return serialized findings associated with this examination.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
obj: PatientExamination instance
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List of finding dictionaries with basic metadata
|
|
58
|
+
"""
|
|
59
|
+
patient_findings = obj.patient_findings.all()
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
"id": pf.id,
|
|
63
|
+
"finding_id": pf.finding.id if pf.finding else None,
|
|
64
|
+
"finding_name": pf.finding.name if pf.finding else None,
|
|
65
|
+
"created_at": pf.created_at if hasattr(pf, "created_at") else None,
|
|
66
|
+
}
|
|
67
|
+
for pf in patient_findings
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class VideoExaminationCreateSerializer(serializers.Serializer):
|
|
72
|
+
"""
|
|
73
|
+
Serializer for creating video examinations via API.
|
|
74
|
+
|
|
75
|
+
Handles the complex creation logic required to link:
|
|
76
|
+
- VideoFile (must exist)
|
|
77
|
+
- Examination type (must exist)
|
|
78
|
+
- Patient (derived from video's SensitiveMeta)
|
|
79
|
+
- New PatientExamination record
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
video_id = serializers.IntegerField(required=True)
|
|
83
|
+
examination_id = serializers.IntegerField(required=True)
|
|
84
|
+
date_start = serializers.DateField(required=False, allow_null=True)
|
|
85
|
+
date_end = serializers.DateField(required=False, allow_null=True)
|
|
86
|
+
|
|
87
|
+
def validate_video_id(self, value):
|
|
88
|
+
"""Ensure video exists"""
|
|
89
|
+
if not VideoFile.objects.filter(id=value).exists():
|
|
90
|
+
raise serializers.ValidationError(f"Video with id {value} does not exist")
|
|
91
|
+
return value
|
|
92
|
+
|
|
93
|
+
def validate_examination_id(self, value):
|
|
94
|
+
"""Ensure examination type exists"""
|
|
95
|
+
if not Examination.objects.filter(id=value).exists():
|
|
96
|
+
raise serializers.ValidationError(
|
|
97
|
+
f"Examination with id {value} does not exist"
|
|
98
|
+
)
|
|
99
|
+
return value
|
|
100
|
+
|
|
101
|
+
def create(self, validated_data):
|
|
102
|
+
"""
|
|
103
|
+
Create PatientExamination record.
|
|
104
|
+
|
|
105
|
+
Links video to examination through patient relationship:
|
|
106
|
+
1. Get video and extract patient from SensitiveMeta
|
|
107
|
+
2. Get examination type
|
|
108
|
+
3. Create PatientExamination linking patient, examination, video
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ValidationError: If video has no patient or sensitive_meta
|
|
112
|
+
"""
|
|
113
|
+
video = VideoFile.objects.get(id=validated_data["video_id"])
|
|
114
|
+
examination = Examination.objects.get(id=validated_data["examination_id"])
|
|
115
|
+
|
|
116
|
+
# Get patient from video's sensitive metadata
|
|
117
|
+
if not hasattr(video, "sensitive_meta") or not video.sensitive_meta:
|
|
118
|
+
raise serializers.ValidationError(
|
|
119
|
+
"Video must have sensitive metadata with patient information"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
sensitive_meta = video.sensitive_meta
|
|
123
|
+
if not sensitive_meta.pseudo_patient:
|
|
124
|
+
raise serializers.ValidationError(
|
|
125
|
+
"Video's sensitive metadata must have an associated pseudo patient"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
patient = sensitive_meta.pseudo_patient
|
|
129
|
+
|
|
130
|
+
# Check if PatientExamination already exists for this video
|
|
131
|
+
existing_exam = PatientExamination.objects.filter(video=video).first()
|
|
132
|
+
if existing_exam:
|
|
133
|
+
# Update existing
|
|
134
|
+
patient_exam = existing_exam
|
|
135
|
+
patient_exam.examination = examination
|
|
136
|
+
if "date_start" in validated_data:
|
|
137
|
+
patient_exam.date_start = validated_data["date_start"]
|
|
138
|
+
if "date_end" in validated_data:
|
|
139
|
+
patient_exam.date_end = validated_data["date_end"]
|
|
140
|
+
patient_exam.save()
|
|
141
|
+
else:
|
|
142
|
+
# Create new
|
|
143
|
+
patient_exam = PatientExamination.objects.create(
|
|
144
|
+
patient=patient,
|
|
145
|
+
examination=examination,
|
|
146
|
+
video=video,
|
|
147
|
+
date_start=validated_data.get("date_start"),
|
|
148
|
+
date_end=validated_data.get("date_end"),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return patient_exam
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class VideoExaminationUpdateSerializer(serializers.Serializer):
|
|
155
|
+
"""
|
|
156
|
+
Serializer for updating video examinations.
|
|
157
|
+
|
|
158
|
+
Allows modification of:
|
|
159
|
+
- Examination type
|
|
160
|
+
- Date range
|
|
161
|
+
- Associated findings (via separate endpoint)
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
examination_id = serializers.IntegerField(required=False)
|
|
165
|
+
date_start = serializers.DateField(required=False, allow_null=True)
|
|
166
|
+
date_end = serializers.DateField(required=False, allow_null=True)
|
|
167
|
+
|
|
168
|
+
def validate_examination_id(self, value):
|
|
169
|
+
"""Ensure examination type exists if provided"""
|
|
170
|
+
if value is not None and not Examination.objects.filter(id=value).exists():
|
|
171
|
+
raise serializers.ValidationError(
|
|
172
|
+
f"Examination with id {value} does not exist"
|
|
173
|
+
)
|
|
174
|
+
return value
|
|
175
|
+
|
|
176
|
+
def update(self, instance, validated_data):
|
|
177
|
+
"""
|
|
178
|
+
Update PatientExamination fields.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
instance: Existing PatientExamination
|
|
182
|
+
validated_data: Validated update data
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Updated PatientExamination instance
|
|
186
|
+
"""
|
|
187
|
+
if "examination_id" in validated_data:
|
|
188
|
+
examination = Examination.objects.get(id=validated_data["examination_id"])
|
|
189
|
+
instance.examination = examination
|
|
190
|
+
|
|
191
|
+
if "date_start" in validated_data:
|
|
192
|
+
instance.date_start = validated_data["date_start"]
|
|
193
|
+
|
|
194
|
+
if "date_end" in validated_data:
|
|
195
|
+
instance.date_end = validated_data["date_end"]
|
|
196
|
+
|
|
197
|
+
instance.save()
|
|
198
|
+
return instance
|
endoreg_db/urls/__init__.py
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
|
-
from django.urls import path, include
|
|
2
1
|
from django.conf import settings
|
|
3
2
|
from django.conf.urls.static import static
|
|
3
|
+
from django.urls import include, path
|
|
4
4
|
from rest_framework.routers import DefaultRouter
|
|
5
5
|
|
|
6
|
-
# Phase 1.2: Media Management URLs ✅ IMPLEMENTED
|
|
7
|
-
from .media import urlpatterns as media_url_patterns
|
|
8
|
-
|
|
9
6
|
from endoreg_db.views import (
|
|
10
|
-
VideoViewSet,
|
|
11
7
|
ExaminationViewSet,
|
|
12
|
-
|
|
8
|
+
FindingClassificationViewSet,
|
|
13
9
|
FindingViewSet,
|
|
14
|
-
|
|
10
|
+
PatientExaminationViewSet,
|
|
15
11
|
PatientFindingViewSet,
|
|
16
|
-
|
|
12
|
+
VideoExaminationViewSet,
|
|
13
|
+
VideoViewSet,
|
|
17
14
|
)
|
|
18
15
|
|
|
19
16
|
from .anonymization import url_patterns as anonymization_url_patterns
|
|
20
|
-
from .classification import url_patterns as classification_url_patterns
|
|
21
17
|
from .auth import urlpatterns as auth_url_patterns
|
|
18
|
+
from .classification import url_patterns as classification_url_patterns
|
|
22
19
|
from .examination import urlpatterns as examination_url_patterns
|
|
23
20
|
from .files import urlpatterns as files_url_patterns
|
|
21
|
+
from .label_video_segment_validate import (
|
|
22
|
+
url_patterns as label_video_segment_validate_url_patterns,
|
|
23
|
+
)
|
|
24
24
|
from .label_video_segments import url_patterns as label_video_segments_url_patterns
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
# Phase 1.2: Media Management URLs ✅ IMPLEMENTED
|
|
27
|
+
from .media import urlpatterns as media_url_patterns
|
|
28
|
+
from .patient import urlpatterns as patient_url_patterns
|
|
29
|
+
|
|
26
30
|
# TODO Phase 1.2: Implement VideoMediaView and PDFMediaView before enabling
|
|
27
31
|
# from .media import urlpatterns as media_url_patterns
|
|
28
32
|
from .report import url_patterns as report_url_patterns
|
|
29
|
-
from .upload import urlpatterns as upload_url_patterns
|
|
30
|
-
from .video import url_patterns as video_url_patterns
|
|
31
33
|
from .requirements import urlpatterns as requirements_url_patterns
|
|
32
|
-
from .patient import urlpatterns as patient_url_patterns
|
|
33
34
|
from .stats import url_patterns as stats_url_patterns
|
|
35
|
+
from .upload import urlpatterns as upload_url_patterns
|
|
36
|
+
from .video import url_patterns as video_url_patterns
|
|
34
37
|
|
|
35
38
|
api_urls = []
|
|
36
39
|
api_urls += classification_url_patterns
|
|
@@ -50,21 +53,31 @@ api_urls += patient_url_patterns
|
|
|
50
53
|
api_urls += stats_url_patterns
|
|
51
54
|
|
|
52
55
|
router = DefaultRouter()
|
|
53
|
-
router.register(r
|
|
54
|
-
router.register(r
|
|
55
|
-
router.register(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
router.register(r
|
|
59
|
-
router.register(r
|
|
56
|
+
router.register(r"videos", VideoViewSet, basename="videos")
|
|
57
|
+
router.register(r"examinations", ExaminationViewSet)
|
|
58
|
+
router.register(
|
|
59
|
+
r"video-examinations", VideoExaminationViewSet, basename="video-examinations"
|
|
60
|
+
)
|
|
61
|
+
router.register(r"findings", FindingViewSet)
|
|
62
|
+
router.register(r"classifications", FindingClassificationViewSet)
|
|
63
|
+
router.register(r"patient-findings", PatientFindingViewSet)
|
|
64
|
+
router.register(r"patient-examinations", PatientExaminationViewSet)
|
|
65
|
+
|
|
66
|
+
# Additional custom video examination routes
|
|
67
|
+
# Frontend expects: GET /api/video/{id}/examinations/
|
|
68
|
+
video_examinations_list = VideoExaminationViewSet.as_view({"get": "by_video"})
|
|
60
69
|
|
|
61
70
|
# Export raw API urlpatterns (no prefix). The project-level endoreg_db/urls.py mounts these under /api/.
|
|
62
71
|
urlpatterns = [
|
|
63
|
-
path(
|
|
64
|
-
|
|
72
|
+
path(
|
|
73
|
+
"video/<int:video_id>/examinations/",
|
|
74
|
+
video_examinations_list,
|
|
75
|
+
name="video-examinations-by-video",
|
|
76
|
+
),
|
|
77
|
+
path("", include(api_urls)), # Specific routes first
|
|
78
|
+
path("", include(router.urls)), # Generic router routes second
|
|
65
79
|
]
|
|
66
80
|
|
|
67
81
|
if settings.DEBUG:
|
|
68
82
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
69
83
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
|
70
|
-
|