diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dd060313..d9f3fdff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -55,6 +55,7 @@ Changed - Update REST API versioning (#1936) - Update REST API views for OpenAPI compatibility (#1951) - Return ``503`` in ``ZoneSubmitMoveAPIView`` if project is locked (#1847) + - Return ``503`` in ``ZoneCreateAPIView`` if no investigation or iRODS collections (#2036) - **Samplesheets** - Update REST API versioning (#1936) - Update REST API views for OpenAPI compatibility (#1951) diff --git a/docs_manual/source/sodar_release_notes.rst b/docs_manual/source/sodar_release_notes.rst index 70a20ea6..c828f121 100644 --- a/docs_manual/source/sodar_release_notes.rst +++ b/docs_manual/source/sodar_release_notes.rst @@ -23,6 +23,8 @@ Release for SODAR Core v1.0 upgrade, iRODS v4.3 upgrade and feature updates. - Update REST API versioning - Update REST API views for OpenAPI support - Update lock requiring REST API views to return 503 if project is locked +- Update landing zone creation REST API view to return 503 if no investigation + or iRODS collections - Upgrade to Postgres v16 - Upgrade to python-irodsclient v2.2.0 - Upgrade to SODAR Core v1.0.2 @@ -73,6 +75,10 @@ REST API Updates * ``IrodsDataRequestAcceptAPIView`` + Return ``503`` if project is locked - Landing Zones API + * ``ZoneCreateAPIView`` + + Return ``503`` if Taskflow is not enabled + + Return ``503`` if investigation for project is not found + + Return ``503`` if project iRODS collections have not been created * ``ZoneSubmitMoveAPIView`` + Return ``503`` if project is locked diff --git a/landingzones/serializers.py b/landingzones/serializers.py index 3e20e5bf..eff514da 100644 --- a/landingzones/serializers.py +++ b/landingzones/serializers.py @@ -1,6 +1,7 @@ """API view model serializers for the landingzone app""" from rest_framework import serializers +from rest_framework.exceptions import APIException # Projectroles dependency from projectroles.plugins import get_backend_api @@ -10,7 +11,7 @@ ) # Samplesheets dependency -from samplesheets.models import Assay +from samplesheets.models import Investigation, Assay from landingzones.constants import ( ZONE_STATUS_OK, @@ -21,6 +22,10 @@ from landingzones.utils import get_zone_title +# Local constants +ZONE_NO_INV_MSG = 'No investigation found for project' + + class LandingZoneSerializer(SODARProjectModelSerializer): """Serializer for the LandingZone model""" @@ -68,6 +73,15 @@ def get_irods_path(self, obj): return irods_backend.get_path(obj) def validate(self, attrs): + # If there is no investigation, we can't have a landing zone + investigation = Investigation.objects.filter( + project=self.context.get('project'), active=True + ).first() + if not investigation: + ex = APIException(ZONE_NO_INV_MSG) + ex.status_code = 503 + raise ex + # Else continue validating the input try: if 'assay' in attrs: assay = Assay.objects.get( diff --git a/landingzones/tests/test_views_api_taskflow.py b/landingzones/tests/test_views_api_taskflow.py index 180a032c..6b3eaaee 100644 --- a/landingzones/tests/test_views_api_taskflow.py +++ b/landingzones/tests/test_views_api_taskflow.py @@ -30,6 +30,7 @@ ZONE_STATUS_FAILED, ) from landingzones.models import LandingZone +from landingzones.serializers import ZONE_NO_INV_MSG from landingzones.tests.test_models import LandingZoneMixin from landingzones.tests.test_views_taskflow import ( LandingZoneTaskflowMixin, @@ -44,6 +45,7 @@ from landingzones.views_api import ( LANDINGZONES_API_MEDIA_TYPE, LANDINGZONES_API_DEFAULT_VERSION, + ZONE_NO_COLLS_MSG, ) @@ -290,7 +292,8 @@ def test_post_no_investigation(self): 'config_data': {}, } response = self.request_knox(self.url, method='POST', data=request_data) - self.assertEqual(response.status_code, 400) + self.assertEqual(response.status_code, 503) + self.assertEqual(ZONE_NO_INV_MSG, response.data['detail']) self.assertEqual(LandingZone.objects.count(), 0) def test_post_no_irods_collections(self): @@ -306,7 +309,8 @@ def test_post_no_irods_collections(self): 'config_data': {}, } response = self.request_knox(self.url, method='POST', data=request_data) - self.assertEqual(response.status_code, 400) + self.assertEqual(response.status_code, 503) + self.assertIn(ZONE_NO_COLLS_MSG, response.data['detail']) self.assertEqual(LandingZone.objects.count(), 0) diff --git a/landingzones/views_api.py b/landingzones/views_api.py index 4464469f..c9ecd05c 100644 --- a/landingzones/views_api.py +++ b/landingzones/views_api.py @@ -5,6 +5,7 @@ from django.urls import reverse +from rest_framework import status from rest_framework.exceptions import APIException, NotFound from rest_framework.generics import ( ListAPIView, @@ -14,7 +15,6 @@ ) from rest_framework.renderers import JSONRenderer from rest_framework.response import Response -from rest_framework import status from rest_framework.serializers import ValidationError from rest_framework.schemas.openapi import AutoSchema from rest_framework.versioning import AcceptHeaderVersioning @@ -51,6 +51,8 @@ LANDINGZONES_API_ALLOWED_VERSIONS = ['1.0'] LANDINGZONES_API_DEFAULT_VERSION = '1.0' +ZONE_NO_COLLS_MSG = 'iRODS collections not created for project' + # Mixins and Base Views -------------------------------------------------------- @@ -214,6 +216,9 @@ class ZoneCreateAPIView( """ Create a landing zone. + Returns ``503`` if an investigation for the project is not found or project + iRODS collections have not been created. + **URL:** ``/landingzones/api/create/{Project.sodar_uuid}`` **Methods:** ``POST`` @@ -237,29 +242,29 @@ class ZoneCreateAPIView( permission_required = 'landingzones.create_zone' serializer_class = LandingZoneSerializer + @classmethod + def _raise_503(cls, msg): + ex = APIException(msg) + ex.status_code = 503 + raise ex + def perform_create(self, serializer): """ Override perform_create() to add timeline event and initiate taskflow. """ - ex_msg = 'Creating landing zone failed: ' + ex_prefix = 'Creating landing zone failed: ' # Check taskflow status if not get_backend_api('taskflow'): - raise APIException('{}Taskflow not enabled'.format(ex_msg)) + self._raise_503('{}Taskflow not enabled'.format(ex_prefix)) # Ensure project has investigation with iRODS collections created project = self.get_project() investigation = Investigation.objects.filter( active=True, project=project ).first() - - if not investigation: - raise ValidationError( - '{}No investigation found for project'.format(ex_msg) - ) + # NOTE: Lack of investigation is already caught in serializer if not investigation.irods_status: - raise ValidationError( - '{}iRODS collections not created for project'.format(ex_msg) - ) + self._raise_503('{}{}'.format(ex_prefix, ZONE_NO_COLLS_MSG)) # If all is OK, go forward with object creation and taskflow submission create_colls = serializer.validated_data.pop('create_colls') @@ -273,7 +278,7 @@ def perform_create(self, serializer): request=self.request, ) except Exception as ex: - raise APIException('{}{}'.format(ex_msg, ex)) + raise APIException('{}{}'.format(ex_prefix, ex)) class ZoneUpdateAPIView(