"""REST API views for the samplesheets app"""
import logging
import re
from irods.exception import CAT_NO_ROWS_FOUND
from irods.models import DataObject
from django.conf import settings
from django.urls import reverse
from rest_framework import status
from rest_framework.exceptions import (
APIException,
ParseError,
ValidationError,
NotFound,
PermissionDenied,
)
from rest_framework.generics import (
CreateAPIView,
DestroyAPIView,
ListAPIView,
RetrieveAPIView,
UpdateAPIView,
)
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
# Projectroles dependency
from projectroles.app_settings import AppSettingAPI
from projectroles.models import RemoteSite
from projectroles.plugins import get_backend_api
from projectroles.views_api import (
SODARAPIBaseMixin,
SODARAPIBaseProjectMixin,
SODARAPIGenericProjectMixin,
)
from projectroles.utils import build_secret
from samplesheets.io import SampleSheetIO
from samplesheets.models import (
Investigation,
ISATab,
IrodsAccessTicket,
IrodsDataRequest,
IRODS_REQUEST_STATUS_ACTIVE,
IRODS_REQUEST_STATUS_FAILED,
)
from samplesheets.rendering import SampleSheetTableBuilder
from samplesheets.serializers import (
InvestigationSerializer,
IrodsAccessTicketSerializer,
IrodsDataRequestSerializer,
)
from samplesheets.views import (
IrodsAccessTicketModifyMixin,
IrodsCollsCreateViewMixin,
IrodsDataRequestModifyMixin,
SheetImportMixin,
SheetISAExportMixin,
SITE_MODE_TARGET,
REMOTE_LEVEL_READ_ROLES,
)
app_settings = AppSettingAPI()
logger = logging.getLogger(__name__)
table_builder = SampleSheetTableBuilder()
MD5_RE = re.compile(r'([a-fA-F\d]{32})')
APP_NAME = 'samplesheets'
IRODS_QUERY_ERROR_MSG = 'Exception querying iRODS objects'
IRODS_REQUEST_EX_MSG = 'iRODS data request failed'
IRODS_TICKET_EX_MSG = 'iRODS access ticket failed'
IRODS_TICKET_NO_UPDATE_FIELDS_MSG = 'No fields to update'
# API Views --------------------------------------------------------------------
[docs]class InvestigationRetrieveAPIView(
SODARAPIGenericProjectMixin, RetrieveAPIView
):
"""
Retrieve metadata of an investigation with its studies and assays.
This view can be used to e.g. retrieve assay UUIDs for landing zone
operations.
**URL:** ``/samplesheets/api/investigation/retrieve/{Project.sodar_uuid}``
**Methods:** ``GET``
**Returns:**
- ``archive_name``: Original archive name if imported from a zip (string)
- ``comments``: Investigation comments (dict)
- ``description``: Investigation description (string)
- ``file_name``: Investigation file name (string)
- ``identifier``: Locally unique investigation identifier (string)
- ``irods_status``: Whether iRODS collections for the investigation have
been created (boolean)
- ``parser_version``: Version of altamISA used in importing (string)
- ``project``: Project UUID (string)
- ``sodar_uuid``: Investigation UUID (string)
- ``studies``: Study and assay information (dict, using study UUID as key)
- ``title``: Investigation title (string)
"""
lookup_field = 'project__sodar_uuid'
permission_required = 'samplesheets.view_sheet'
serializer_class = InvestigationSerializer
[docs]class IrodsCollsCreateAPIView(
IrodsCollsCreateViewMixin, SODARAPIBaseProjectMixin, APIView
):
"""
Create iRODS collections for a project.
**URL:** ``/samplesheets/api/irods/collections/create/{Project.sodar_uuid}``
**Methods:** ``POST``
**Returns:**
- ``path``: Full iRODS path to the root of created collections (string)
"""
http_method_names = ['post']
permission_required = 'samplesheets.create_colls'
def post(self, request, *args, **kwargs):
"""POST request for creating iRODS collections"""
irods_backend = get_backend_api('omics_irods')
ex_msg = 'Creating iRODS collections failed: '
investigation = Investigation.objects.filter(
project__sodar_uuid=self.kwargs.get('project'), active=True
).first()
if not investigation:
raise ValidationError('{}Investigation not found'.format(ex_msg))
# TODO: TBD: Also allow updating?
if investigation.irods_status:
raise ValidationError(
'{}iRODS collections already created'.format(ex_msg)
)
try:
self.create_colls(investigation, request)
except Exception as ex:
raise APIException('{}{}'.format(ex_msg, ex))
return Response(
{
'detail': 'iRODS collections created',
'path': irods_backend.get_sample_path(investigation.project),
},
status=status.HTTP_200_OK,
)
[docs]class SheetISAExportAPIView(
SheetISAExportMixin, SODARAPIBaseProjectMixin, APIView
):
"""
Export sample sheets as ISA-Tab TSV files, either packed in a zip archive or
wrapped in a JSON structure.
**URL for zip export:** ``/samplesheets/api/export/zip/{Project.sodar_uuid}``
**URL for JSON export:** ``/samplesheets/api/export/json/{Project.sodar_uuid}``
**Methods:** ``GET``
"""
http_method_names = ['get']
permission_required = 'samplesheets.export_sheet'
def get(self, request, *args, **kwargs):
project = self.get_project()
investigation = Investigation.objects.filter(
project=project, active=True
).first()
if not investigation:
raise NotFound()
export_format = 'json'
if self.request.get_full_path() == reverse(
'samplesheets:api_export_zip',
kwargs={'project': project.sodar_uuid},
):
export_format = 'zip'
try:
return self.get_isa_export(project, request, export_format)
except Exception as ex:
raise APIException('Unable to export ISA-Tab: {}'.format(ex))
[docs]class SheetImportAPIView(SheetImportMixin, SODARAPIBaseProjectMixin, APIView):
"""
Upload sample sheet as separate ISA-Tab TSV files or a zip archive. Will
replace existing sheets if valid.
The request should be in format of ``multipart/form-data``. Content type
for each file must be provided.
**URL:** ``/samplesheets/api/import/{Project.sodar_uuid}``
**Methods:** ``POST``
**Returns:**
- ``detail``: Detail of project success (string)
- ``sodar_warnings``: SODAR import issue warnings (list of srings, optional)
"""
http_method_names = ['post']
permission_required = 'samplesheets.edit_sheet'
def post(self, request, *args, **kwargs):
"""Handle POST request for submitting"""
project = self.get_project()
if app_settings.get(APP_NAME, 'sheet_sync_enable', project):
raise ValidationError(
'Sheet synchronization enabled in project: import not allowed'
)
sheet_io = SampleSheetIO()
old_inv = Investigation.objects.filter(
project=project, active=True
).first()
zip_file = None
if len(request.FILES) == 0:
raise ParseError('No files provided')
# Zip file handling
if len(request.FILES) == 1:
file = request.FILES[next(iter(request.FILES))]
try:
zip_file = sheet_io.get_zip_file(file)
except OSError as ex:
raise ParseError('Failed to parse zip archive: {}'.format(ex))
isa_data = sheet_io.get_isa_from_zip(zip_file)
# Multi-file handling
else:
try:
isa_data = sheet_io.get_isa_from_files(request.FILES.values())
except Exception as ex:
raise ParseError('Failed to parse TSV files: {}'.format(ex))
# Handle import
action = 'replace' if old_inv else 'create'
tl_event = self.add_tl_event(project=project, action=action)
try:
investigation = sheet_io.import_isa(
isa_data=isa_data,
project=project,
archive_name=zip_file.filename if zip_file else None,
user=request.user,
replace=True if old_inv else False,
replace_uuid=old_inv.sodar_uuid if old_inv else None,
)
except Exception as ex:
self.handle_import_exception(ex, tl_event, ui_mode=False)
raise APIException(str(ex))
# Handle replace
if old_inv:
try:
investigation = self.handle_replace(
investigation=investigation,
old_inv=old_inv,
tl_event=tl_event,
)
ex_msg = None
except Exception as ex:
ex_msg = str(ex)
if ex_msg or not investigation:
raise ParseError(
'Sample sheet replacing failed: {}'.format(
ex_msg if ex_msg else 'See SODAR error log'
)
)
if tl_event:
tl_event.add_object(
obj=investigation,
label='investigation',
name=investigation.title,
)
# Finalize import
isa_version = (
ISATab.objects.filter(investigation_uuid=investigation.sodar_uuid)
.order_by('-date_created')
.first()
)
self.finalize_import(
investigation=investigation,
action=action,
tl_event=tl_event,
isa_version=isa_version,
)
ret_data = {
'detail': 'Sample sheets {}d for project {}'.format(
action, project.get_log_title()
)
}
no_plugin_assays = self.get_assays_without_plugins(investigation)
if no_plugin_assays:
ret_data['sodar_warnings'] = [
self.get_assay_plugin_warning(a) for a in no_plugin_assays
]
return Response(ret_data, status=status.HTTP_200_OK)
[docs]class IrodsAccessTicketRetrieveAPIView(
SODARAPIGenericProjectMixin, RetrieveAPIView
):
"""
Retrieve an iRODS access ticket for a project.
**URL:** ``/samplesheets/api/irods/ticket/retrieve/{IrodsAccessTicket.sodar_uuid}``
**Methods:** ``GET``
**Returns**
- ``path``: Full iRODS path (string)
- ``label``: Text label for ticket (string, optional)
- ``ticket``: Ticket string for accessing the path (string)
- ``assay``: Assay UUID (string)
- ``study``: Study UUID (string)
- ``date_created``: Creation datetime (YYYY-MM-DDThh:mm:ssZ)
- ``date_expires``: Expiry datetime (YYYY-MM-DDThh:mm:ssZ or null)
- ``user``: User who created the request (SODARUserSerializer dict)
- ``is_active``: Whether the request is currently active (boolean)
- ``sodar_uuid``: IrodsAccessTicket UUID (string)
"""
lookup_field = 'sodar_uuid'
lookup_url_kwarg = 'irodsaccessticket'
permission_required = 'samplesheets.edit_sheet'
serializer_class = IrodsAccessTicketSerializer
queryset_project_field = 'study__investigation__project'
[docs]class IrodsAccessTicketListAPIView(SODARAPIBaseProjectMixin, ListAPIView):
"""
List iRODS access tickets for a project.
**URL:** ``/samplesheets/api/irods/ticket/list/{Project.sodar_uuid}``
**Methods:** ``GET``
**Query parameters:**
- ``active`` (boolean, optional, default=false)
**Returns:** List of ticket dicts, see ``IrodsAccessTicketRetrieveAPIView``
"""
permission_required = 'samplesheets.edit_sheet'
serializer_class = IrodsAccessTicketSerializer
def get_queryset(self):
project = self.get_project()
tickets = IrodsAccessTicket.objects.filter(
study__investigation__project=project
)
active = self.request.query_params.get('active', '0')
active = bool(int(active))
if active:
tickets = [t for t in tickets if t.is_active()]
return tickets
[docs]class IrodsAccessTicketCreateAPIView(
IrodsAccessTicketModifyMixin, SODARAPIGenericProjectMixin, CreateAPIView
):
"""
Create an iRODS access ticket for a project.
**URL:** ``/samplesheets/api/irods/ticket/create/{Project.sodar_uuid}``
**Methods:** ``POST``
**Parameters:**
- ``path``: Full iRODS path (string)
- ``label``: Text label for ticket (string, optional)
- ``date_expires``: Expiration date (YYYY-MM-DDThh:mm:ssZ, optional)
**Returns:** Ticket dict, see ``IrodsAccessTicketRetrieveAPIView``
"""
permission_required = 'samplesheets.edit_sheet'
serializer_class = IrodsAccessTicketSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context['project'] = self.get_project()
context['user'] = self.request.user
return context
def perform_create(self, serializer):
"""Override perform_create() to create IrodsAccessTicket"""
irods_backend = get_backend_api('omics_irods')
try:
with irods_backend.get_session() as irods:
ticket = irods_backend.issue_ticket(
irods,
'read',
serializer.validated_data.get('path'),
ticket_str=build_secret(16),
expiry_date=serializer.validated_data.get('date_expires'),
)
except Exception as ex:
raise ValidationError(
'{} {}'.format('Creating ' + IRODS_TICKET_EX_MSG + ':', ex)
)
serializer.validated_data['ticket'] = ticket.ticket
serializer.save()
# Create timeline event
self.add_tl_event(serializer.instance, 'create')
# Add app alerts to owners/delegates
self.create_app_alerts(serializer.instance, 'create', self.request.user)
[docs]class IrodsAccessTicketUpdateAPIView(
IrodsAccessTicketModifyMixin, SODARAPIGenericProjectMixin, UpdateAPIView
):
"""
Update an iRODS access ticket for a project.
**URL:** ``/samplesheets/api/irods/ticket/update/{IrodsAccessTicket.sodar_uuid}``
**Methods:** ``PUT``, ``PATCH``
**Parameters:**
- ``label``: Label (string)
- ``date_expires``: Expiration date (YYYY-MM-DDThh:mm:ssZ, optional)
**Returns:** Ticket dict, see ``IrodsAccessTicketRetrieveAPIView``
"""
lookup_url_kwarg = 'irodsaccessticket'
permission_required = 'samplesheets.edit_sheet'
serializer_class = IrodsAccessTicketSerializer
queryset_project_field = 'study__investigation__project'
def perform_update(self, serializer):
"""Override perform_update() to update IrodsAccessTicket"""
if not set(serializer.initial_data) & {'label', 'date_expires'}:
raise ValidationError(IRODS_TICKET_NO_UPDATE_FIELDS_MSG)
serializer.save()
# Add timeline event
self.add_tl_event(serializer.instance, 'update')
# Add app alerts to owners/delegates
self.create_app_alerts(serializer.instance, 'update', self.request.user)
[docs]class IrodsAccessTicketDestroyAPIView(
IrodsAccessTicketModifyMixin, SODARAPIGenericProjectMixin, DestroyAPIView
):
"""
Delete an iRODS access ticket.
**URL:** ``/samplesheets/api/irods/ticket/delete/{IrodsAccessTicket.sodar_uuid}``
**Methods:** ``DELETE``
"""
lookup_field = 'sodar_uuid'
lookup_url_kwarg = 'irodsaccessticket'
permission_required = 'samplesheets.edit_sheet'
serializer_class = IrodsAccessTicketSerializer
queryset_project_field = 'study__investigation__project'
def perform_destroy(self, instance):
"""Override perform_destroy() to delete IrodsAccessTicket"""
irods_backend = get_backend_api('omics_irods')
try:
with irods_backend.get_session() as irods:
irods_backend.delete_ticket(irods, instance.ticket)
except Exception as ex:
raise ValidationError(
'{} {}'.format('Deleting ' + IRODS_TICKET_EX_MSG + ':', ex)
)
instance.delete()
# Create timeline event
self.add_tl_event(instance, 'delete')
# Add app alerts to owners/delegates
self.create_app_alerts(instance, 'delete', self.request.user)
[docs]class IrodsDataRequestRetrieveAPIView(
SODARAPIGenericProjectMixin, RetrieveAPIView
):
"""
Retrieve a iRODS data request.
**URL:** ``/samplesheets/api/irods/request/retrieve/{IrodsDataRequest.sodar_uuid}``
**Methods:** ``GET``
**Returns:**
- ``project``: Project UUID (string)
- ``action``: Request action (string)
- ``path``: iRODS path to object or collection (string)
- ``target_path``: Target path (string, currently unused)
- ``user``: User initiating request (dict)
- ``status``: Request status (string)
- ``status_info``: Request status info (string)
- ``description``: Request description (string)
- ``date_created``: Request creation date (datetime)
- ``sodar_uuid``: Request UUID (string)
"""
lookup_field = 'sodar_uuid'
lookup_url_kwarg = 'irodsdatarequest'
permission_required = 'samplesheets.edit_sheet'
serializer_class = IrodsDataRequestSerializer
[docs]class IrodsDataRequestListAPIView(SODARAPIBaseProjectMixin, ListAPIView):
"""
List the iRODS data requests for a project.
If the requesting user is an owner, delegate or superuser, the view lists
all requests with the status of ACTIVE or FAILED. If called as a
contributor, returns the user's own requests regardless of the state.
**URL:** ``/samplesheets/api/irods/requests/{Project.sodar_uuid}``
**Methods:** ``GET``
**Returns:** List of iRODS data requests (list of dicts)
"""
permission_required = 'samplesheets.edit_sheet'
serializer_class = IrodsDataRequestSerializer
def get_queryset(self):
project = self.get_project()
requests = IrodsDataRequest.objects.filter(project=project)
# For superusers, owners and delegates, display requests from all users
if self.request.user.is_superuser or project.is_owner_or_delegate(
self.request.user
):
return requests.filter(
status__in=[
IRODS_REQUEST_STATUS_ACTIVE,
IRODS_REQUEST_STATUS_FAILED,
]
)
return requests.filter(user=self.request.user)
[docs]class IrodsDataRequestCreateAPIView(
IrodsDataRequestModifyMixin, SODARAPIGenericProjectMixin, CreateAPIView
):
"""
Create an iRODS delete request for a project.
The request must point to a collection or data object within the sample data
repository of the project. The user making the request must have the role of
contributor or above in the project.
**URL:** ``/samplesheets/api/irods/request/create/{Project.sodar_uuid}``
**Methods:** ``POST``
**Parameters:**
- ``path``: iRODS path to object or collection (string)
- ``description``: Request description (string, optional)
"""
permission_required = 'samplesheets.edit_sheet'
serializer_class = IrodsDataRequestSerializer
def perform_create(self, serializer):
serializer.save()
# Create timeline event
self.add_tl_event(serializer.instance, 'create')
# Add app alerts to owners/delegates
self.add_alerts_create(serializer.instance.project)
[docs]class IrodsDataRequestUpdateAPIView(
IrodsDataRequestModifyMixin, SODARAPIGenericProjectMixin, UpdateAPIView
):
"""
Update an iRODS data request for a project.
**URL:** ``/samplesheets/api/irods/request/update/{IrodsDataRequest.sodar_uuid}``
**Methods:** ``PUT``, ``PATCH``
**Parameters:**
- ``path``: iRODS path to object or collection (string)
- ``description``: Request description
"""
lookup_url_kwarg = 'irodsdatarequest'
permission_classes = [IsAuthenticated]
serializer_class = IrodsDataRequestSerializer
def perform_update(self, serializer):
if not self.has_irods_request_update_perms(
self.request, serializer.instance
):
raise PermissionDenied
serializer.save()
# Add timeline event
self.add_tl_event(serializer.instance, 'update')
[docs]class IrodsDataRequestDestroyAPIView(
IrodsDataRequestModifyMixin, SODARAPIGenericProjectMixin, DestroyAPIView
):
"""
Delete an iRODS data request object.
This action only deletes the request object and is equvalent to cencelling
the request. No associated iRODS collections or data objects will be
deleted.
**URL:** ``/samplesheets/api/irods/request/delete/{IrodsDataRequest.sodar_uuid}``
**Methods:** ``DELETE``
"""
lookup_url_kwarg = 'irodsdatarequest'
permission_classes = [IsAuthenticated]
serializer_class = IrodsDataRequestSerializer
def perform_destroy(self, instance):
if not self.has_irods_request_update_perms(self.request, instance):
raise PermissionDenied
instance.delete()
# Add timeline event
self.add_tl_event(instance, 'delete')
# Handle project alerts
self.handle_alerts_deactivate(instance)
[docs]class IrodsDataRequestAcceptAPIView(
IrodsDataRequestModifyMixin, SODARAPIBaseProjectMixin, APIView
):
"""
Accept an iRODS data request for a project.
Accepting will delete the iRODS collection or data object targeted by the
request. This action can not be undone.
**URL:** ``/samplesheets/api/irods/request/accept/{IrodsDataRequest.sodar_uuid}``
**Methods:** ``POST``
"""
http_method_names = ['post']
permission_required = 'samplesheets.manage_sheet'
def post(self, request, *args, **kwargs):
"""POST request for accepting an iRODS data request"""
timeline = get_backend_api('timeline_backend')
taskflow = get_backend_api('taskflow')
app_alerts = get_backend_api('appalerts_backend')
project = self.get_project()
irods_request = IrodsDataRequest.objects.filter(
sodar_uuid=self.kwargs.get('irodsdatarequest')
).first()
try:
self.accept_request(
irods_request,
project,
self.request,
timeline=timeline,
taskflow=taskflow,
app_alerts=app_alerts,
)
except Exception as ex:
raise ValidationError(
'{} {}'.format('Accepting ' + IRODS_REQUEST_EX_MSG + ':', ex)
)
return Response(
{'detail': 'iRODS data request accepted'}, status=status.HTTP_200_OK
)
[docs]class IrodsDataRequestRejectAPIView(
IrodsDataRequestModifyMixin, SODARAPIBaseProjectMixin, APIView
):
"""
Reject an iRODS data request for a project.
This action will set the request status as rejected and keep the targeted
iRODS collection or data object intact.
**URL:** ``/samplesheets/api/irods/request/reject/{IrodsDataRequest.sodar_uuid}``
**Methods:** ``POST``
"""
http_method_names = ['post']
permission_required = 'samplesheets.manage_sheet'
def post(self, request, *args, **kwargs):
"""POST request for rejecting an iRODS data request"""
timeline = get_backend_api('timeline_backend')
app_alerts = get_backend_api('appalerts_backend')
project = self.get_project()
irods_request = IrodsDataRequest.objects.filter(
sodar_uuid=self.kwargs.get('irodsdatarequest')
).first()
try:
self.reject_request(
irods_request,
project,
self.request,
timeline=timeline,
app_alerts=app_alerts,
)
except Exception as ex:
raise APIException(
'{} {}'.format('Rejecting ' + IRODS_REQUEST_EX_MSG + ':', ex)
)
return Response(
{'detail': 'iRODS data request rejected'}, status=status.HTTP_200_OK
)
[docs]class SampleDataFileExistsAPIView(SODARAPIBaseMixin, APIView):
"""
Return status of data object existing in SODAR iRODS by MD5 checksum.
Includes all projects in search regardless of user permissions.
**URL:** ``/samplesheets/api/file/exists``
**Methods:** ``GET``
**Parameters:**
- ``checksum``: MD5 checksum (string)
**Returns:**
- ``detail``: String
- ``status``: Boolean
"""
http_method_names = ['get']
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
if not settings.ENABLE_IRODS:
raise APIException('iRODS not enabled')
irods_backend = get_backend_api('omics_irods')
if not irods_backend:
raise APIException('iRODS backend not enabled')
c = request.query_params.get('checksum')
if not c or not re.match(MD5_RE, c):
raise ParseError('Invalid MD5 checksum: "{}"'.format(c))
ret = {'detail': 'File does not exist', 'status': False}
sql = (
'SELECT DISTINCT ON (data_id) data_name '
'FROM r_data_main JOIN r_coll_main USING (coll_id) '
'WHERE (coll_name LIKE \'%/{coll}\' '
'OR coll_name LIKE \'%/{coll}/%\') '
'AND r_data_main.data_checksum = \'{sum}\''.format(
coll=settings.IRODS_SAMPLE_COLL, sum=c
)
)
# print('QUERY: {}'.format(sql)) # DEBUG
columns = [DataObject.name]
try:
with irods_backend.get_session() as irods:
query = irods_backend.get_query(irods, sql, columns)
try:
results = query.get_results()
if sum(1 for _ in results) > 0:
ret['detail'] = 'File exists'
ret['status'] = True
except CAT_NO_ROWS_FOUND:
pass # No results, this is OK
except Exception as ex:
logger.error(
'{} iRODS query exception: {}'.format(
self.__class__.__name__, ex
)
)
raise APIException(
'iRODS query exception, please contact an admin if '
'issue persists'
)
finally:
query.remove()
except Exception as ex:
return Response(
{'detail': 'Unable to connect to iRODS: {}'.format(ex)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return Response(ret, status=status.HTTP_200_OK)
[docs]class ProjectIrodsFileListAPIView(SODARAPIBaseProjectMixin, APIView):
"""
Return a list of files in the project sample data repository.
**URL:** ``/samplesheets/api/file/list/{Project.sodar_uuid}``
**Methods:** ``GET``
**Returns:**
- ``irods_data``: List of iRODS data objects (list of dicts)
"""
http_method_names = ['get']
permission_required = 'samplesheets.view_sheet'
def get(self, request, *args, **kwargs):
if not settings.ENABLE_IRODS:
raise APIException('iRODS not enabled')
irods_backend = get_backend_api('omics_irods')
project = self.get_project()
path = irods_backend.get_sample_path(project)
try:
with irods_backend.get_session() as irods:
obj_list = irods_backend.get_objects(irods, path)
except Exception as ex:
return Response(
{'detail': '{}: {}'.format(IRODS_QUERY_ERROR_MSG, ex)},
status=status.HTTP_404_NOT_FOUND,
)
return Response({'irods_data': obj_list}, status=status.HTTP_200_OK)
# TODO: Temporary HACK, should be replaced by proper API view
class RemoteSheetGetAPIView(APIView):
"""
Temporary API view for retrieving the sample sheet as JSON by a target
site, either as rendered tables or the original ISA-Tab.
"""
permission_classes = (AllowAny,) # We check the secret in get()/post()
def get(self, request, **kwargs):
secret = kwargs['secret']
try:
target_site = RemoteSite.objects.get(
mode=SITE_MODE_TARGET, secret=secret
)
except RemoteSite.DoesNotExist:
return Response('Remote site not found, unauthorized', status=401)
target_project = target_site.projects.filter(
project_uuid=kwargs['project']
).first()
if (
not target_project
or target_project.level != REMOTE_LEVEL_READ_ROLES
):
return Response(
'No project access for remote site, unauthorized', status=401
)
try:
investigation = Investigation.objects.get(
project=target_project.get_project(), active=True
)
except Investigation.DoesNotExist:
return Response(
'No ISA investigation found for project', status=404
)
# All OK so far, return data
isa = request.GET.get('isa')
# Rendered tables
if not isa or int(isa) != 1:
ret = {'studies': {}}
# Get/build study tables
for study in investigation.studies.all():
try:
tables = table_builder.get_study_tables(study)
except Exception as ex:
return Response(str(ex), status=500)
ret['studies'][str(study.sodar_uuid)] = tables
# Original ISA-Tab
else:
sheet_io = SampleSheetIO()
try:
ret = sheet_io.export_isa(investigation)
except Exception as ex:
return Response(str(ex), status=500)
return Response(ret, status=200)