diff --git a/promort/VERSION b/promort/VERSION index f514a2f..f76f913 100644 --- a/promort/VERSION +++ b/promort/VERSION @@ -1 +1 @@ -0.9.1 \ No newline at end of file +0.9.2 \ No newline at end of file diff --git a/promort/predictions_manager/migrations/0003_prediction_review_required.py b/promort/predictions_manager/migrations/0003_prediction_review_required.py new file mode 100644 index 0000000..348bd29 --- /dev/null +++ b/promort/predictions_manager/migrations/0003_prediction_review_required.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.13 on 2022-02-18 14:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('predictions_manager', '0002_auto_20210915_1608'), + ] + + operations = [ + migrations.AddField( + model_name='prediction', + name='review_required', + field=models.BooleanField(default=False), + ), + ] diff --git a/promort/predictions_manager/models.py b/promort/predictions_manager/models.py index 1ba5a40..06b667d 100644 --- a/promort/predictions_manager/models.py +++ b/promort/predictions_manager/models.py @@ -37,6 +37,11 @@ class Prediction(models.Model): type = models.CharField(max_length=7, choices=PREDICTION_TYPES, blank=False, null=False) omero_id = models.IntegerField(blank=True, null=True, default=None) provenance = models.TextField(blank=True, null=True) + review_required = models.BooleanField(blank=False, null=False, default=False) + + def require_review(self): + self.review_required = True + self.save() class TissueFragmentsCollection(models.Model): diff --git a/promort/predictions_manager/serializers.py b/promort/predictions_manager/serializers.py index 5ca225f..497ed54 100644 --- a/promort/predictions_manager/serializers.py +++ b/promort/predictions_manager/serializers.py @@ -32,7 +32,8 @@ class PredictionSerializer(serializers.ModelSerializer): class Meta: model = Prediction - fields = ('id', 'label', 'creation_date', 'slide', 'type', 'omero_id', 'provenance') + fields = ('id', 'label', 'creation_date', 'slide', 'type', 'omero_id', 'provenance', + 'review_required') read_only_fields = ('id', 'creation_date') def validate_provenance(self, value): @@ -48,8 +49,9 @@ class PredictionDetailsSerializer(serializers.ModelSerializer): class Meta: model = Prediction - fields = ('id', 'label', 'creation_date', 'slide', 'type', 'omero_id', 'provenance') - read_only_fields = ('id', 'label', 'creation_date', 'slide', 'type', 'omero_id', 'provenance') + fields = ('id', 'label', 'creation_date', 'slide', 'type', 'omero_id', 'provenance', 'review_required') + read_only_fields = ('id', 'label', 'creation_date', 'slide', 'type', 'omero_id', 'provenance', + 'review_required') class TissueFragmentsCollectionSerializer(serializers.ModelSerializer): diff --git a/promort/predictions_manager/views.py b/promort/predictions_manager/views.py index 4112167..b8b2eee 100644 --- a/promort/predictions_manager/views.py +++ b/promort/predictions_manager/views.py @@ -46,6 +46,22 @@ class PredictionDetail(GenericDetailView): permission_classes = (permissions.IsAuthenticated, ) +class PredictionRequireReview(APIView): + permission_classes = (permissions.IsAuthenticated, ) + + def _find_prediction(self, label): + try: + prediction = Prediction.objects.get(label=label) + return prediction + except Prediction.DoesNotExist: + raise NotFound(f'No prediction review item with label \'{label}\'') + + def put(self, request, label, format=None): + prediction = self._find_prediction(label) + prediction.require_review() + return Response(status=status.HTTP_204_NO_CONTENT) + + class TissueFragmentsCollectionList(GenericListView): model = TissueFragmentsCollection model_serializer = TissueFragmentsCollectionSerializer diff --git a/promort/promort/urls.py b/promort/promort/urls.py index b86fe86..bac5379 100644 --- a/promort/promort/urls.py +++ b/promort/promort/urls.py @@ -43,7 +43,7 @@ class NumericString: - regex = '[0-9]+' + regex = r'[0-9]+' def to_python(self, value): return value @@ -52,11 +52,18 @@ def to_url(self, value): return value -class SemiSlug: - """ - A slug comprising letters from A to F - """ - regex = r'[A-Fa-f0-9\-.]+' +class RandomCaseLabel: + regex = r'[A-Fa-f0-9]+' + + def to_python(self, value): + return value + + def to_url(self, value): + return value + + +class RandomSlideLabel: + regex = r'[A-Fa-f0-9]+\-[A-Za-z0-9]+' def to_python(self, value): return value @@ -66,7 +73,8 @@ def to_url(self, value): register_converter(NumericString, 'num') -register_converter(SemiSlug, 'semislug') +register_converter(RandomCaseLabel, 'rclabel') +register_converter(RandomSlideLabel, 'rslabel') urlpatterns = [ # authentication @@ -118,14 +126,13 @@ def to_url(self, value): qmv.QuestionnairePanelAnswersDetail.as_view()), # ROIs annotation steps details - path( - 'api/rois_annotation_steps//clinical_annotation_steps/', - rmv.ClinicalAnnotationStepsList.as_view()), + path('api/rois_annotation_steps//clinical_annotation_steps/', + rmv.ClinicalAnnotationStepsList.as_view()), # ROIs - path('api/rois_annotation_steps//rois_list/', + path('api/rois_annotation_steps//rois_list/', ROIsTreeList.as_view()), - path('api/rois_annotation_steps//slices/', + path('api/rois_annotation_steps//slices/', SliceList.as_view()), path('api/slices//cores/', CoreList.as_view()), path('api/slices//', SliceDetail.as_view()), @@ -135,38 +142,38 @@ def to_url(self, value): # clinical annotations data path( - 'api/rois_annotation_steps//rois_list//', + 'api/rois_annotation_steps//rois_list//', AnnotatedROIsTreeList.as_view()), path( - 'api/clinical_annotation_steps//annotations_list/', + 'api/clinical_annotation_steps//annotations_list/', ClinicalAnnotationStepAnnotationsList.as_view()), path('api/slices//clinical_annotations/', SliceAnnotationList.as_view()), - path('api/slices//clinical_annotations//', + path('api/slices//clinical_annotations//', SliceAnnotationDetail.as_view()), path('api/cores//clinical_annotations/', CoreAnnotationList.as_view()), - path('api/cores//clinical_annotations//', + path('api/cores//clinical_annotations//', CoreAnnotationDetail.as_view()), path('api/focus_regions//clinical_annotations/', FocusRegionAnnotationList.as_view()), path( - 'api/focus_regions//clinical_annotations//', + 'api/focus_regions//clinical_annotations//', FocusRegionAnnotationDetail.as_view()), # ROIs annotations path('api/rois_annotations/', rmv.ROIsAnnotationsList.as_view()), - path('api/rois_annotations/annotations//', + path('api/rois_annotations/annotations//', rmv.ROIsAnnotationDetail.as_view()), - path('api/rois_annotations/steps//reset/', + path('api/rois_annotations/steps//reset/', rmv.ROIsAnnotationStepReopen.as_view()), - path('api/rois_annotations/steps//', + path('api/rois_annotations/steps//', rmv.ROIsAnnotationStepDetail.as_view()), path('api/rois_annotations//', rmv.ROIsAnnotationsDetail.as_view()), # quality control - path('api/rois_annotations/steps//slide_evaluation/', + path('api/rois_annotations/steps//slide_evaluation/', SlideEvaluationDetail.as_view()), path('api/rois_annotations///', rmv.ROIsAnnotationCreation.as_view()), @@ -177,9 +184,9 @@ def to_url(self, value): path('api/clinical_annotations/', rmv.ClinicalAnnotationsList.as_view()), path('api/clinical_annotations//', rmv.ClinicalAnnotationsDetail.as_view()), - path('api/clinical_annotations/annotations//', + path('api/clinical_annotations/annotations//', rmv.ClinicalAnnotationDetail.as_view()), - path('api/clinical_annotations/steps//', + path('api/clinical_annotations/steps//', rmv.ClinicalAnnotationStepDetail.as_view()), path( 'api/clinical_annotations////', @@ -191,12 +198,13 @@ def to_url(self, value): # predictions reviews path('api/prediction_reviews/', rmv.PredictionReviewsList.as_view()), path('api/prediction_reviews//', rmv.PredictionReviewsDetail.as_view()), - path('api/prediction_review//', rmv.PredictionReviewDetail.as_view()), - path('api/prediction_review//prediction/', rmv.PredictionByReviewDetail.as_view()), + path('api/prediction_review//', rmv.PredictionReviewDetail.as_view()), + path('api/prediction_review//prediction/', rmv.PredictionByReviewDetail.as_view()), # predictions path('api/predictions/', pmv.PredictionList.as_view()), path('api/predictions//', pmv.PredictionDetail.as_view()), + path('api/predictions//require_review/', pmv.PredictionRequireReview.as_view()), # tissue fragments path('api/tissue_fragments_collections/', @@ -206,15 +214,14 @@ def to_url(self, value): ), # GET, DELETE, PUT (se usi GenericDetailView) path('api/tissue_fragments_collections//fragments/', pmv.TissueFragmentList.as_view()), # POST - path( - 'api/tissue_fragments_collections//fragments//', - pmv.TissueFragmentsDetail.as_view()), # DELETE + path('api/tissue_fragments_collections//fragments//', + pmv.TissueFragmentsDetail.as_view()), # DELETE # worklists path('api/worklist/', UserWorkList.as_view()), - path('api/worklist/rois_annotations//', + path('api/worklist/rois_annotations//', UserWorklistROIsAnnotation.as_view()), - path('api/worklist/clinical_annotations//', + path('api/worklist/clinical_annotations//', UserWorklistClinicalAnnotation.as_view()), path('api/worklist/admin//', WorkListAdmin.as_view()), diff --git a/promort/reviews_manager/management/commands/build_prediction_reviews_worklist.py b/promort/reviews_manager/management/commands/build_prediction_reviews_worklist.py index 520667f..63adc08 100644 --- a/promort/reviews_manager/management/commands/build_prediction_reviews_worklist.py +++ b/promort/reviews_manager/management/commands/build_prediction_reviews_worklist.py @@ -48,7 +48,7 @@ def _get_prediction_reviews_manager_users(self): return prev_manager_group.user_set.all() def _get_predictions_list(self, prediction_type): - return Prediction.objects.filter(type=prediction_type).all() + return Prediction.objects.filter(type=prediction_type, review_required=True).all() def _check_duplicated(self, prediction, reviewer): annotation_objs = PredictionReview.objects.filter(prediction=prediction, reviewer=reviewer)