Skip to content

Commit

Permalink
remove validation repetition (#1706)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Jul 31, 2023
1 parent 31b575a commit cbf108d
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 118 deletions.
145 changes: 80 additions & 65 deletions samplesheets/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
TPL_DIR_FIELD = '__output_dir'


# Mixins and Helpers -----------------------------------------------------------


class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True

Expand All @@ -55,6 +58,72 @@ def clean(self, data, initial=None):
return result


class IrodsDataRequestValidateMixin:
"""Validation helpers for iRODS data requests"""

def validate_request_path(self, irods_backend, project, instance, path):
"""
Validate path for IrodsAccessRequest.
:param irods_backend: IrodsAPI object
:param project: Project object
:param instance: Existing instance in case of update or None
:param path: Full iRODS path to a collection or a data object (string)
:raises: ValueError if path is incorrect
"""
old_request = IrodsDataRequest.objects.filter(
path=path,
status__in=[
IRODS_REQUEST_STATUS_ACTIVE,
IRODS_REQUEST_STATUS_FAILED,
],
).first()
if old_request and old_request != instance:
raise ValueError(ERROR_MSG_EXISTING)

path_re = re.compile(
'^' + irods_backend.get_projects_path() + '/[0-9a-f]{2}/'
'(?P<project_uuid>[0-9a-f-]{36})/'
+ settings.IRODS_SAMPLE_COLL
+ '/study_(?P<study_uuid>[0-9a-f-]{36})/'
'assay_(?P<assay_uuid>[0-9a-f-]{36})/.+$'
)
match = re.search(path_re, path)
if not match:
raise ValueError(ERROR_MSG_INVALID_PATH)

project_path = irods_backend.get_path(project)
if not path.startswith(project_path):
raise ValueError('Path does not belong in project')

try:
Study.objects.get(
sodar_uuid=match.group('study_uuid'),
investigation__project__sodar_uuid=match.group('project_uuid'),
)
except Study.DoesNotExist:
raise ValueError('Study not found in project with UUID')
try:
Assay.objects.get(
sodar_uuid=match.group('assay_uuid'),
study__sodar_uuid=match.group('study_uuid'),
)
except Assay.DoesNotExist:
raise ValueError('Assay not found in this project with UUID')

with irods_backend.get_session() as irods:
if path and not (
irods.data_objects.exists(path)
or irods.collections.exists(path)
):
raise ValueError(
'Path to collection or data object doesn\'t exist in iRODS',
)


# Forms ------------------------------------------------------------------------


class SheetImportForm(forms.Form):
"""
Form for importing an ISA investigation from an ISA-Tab archive or
Expand Down Expand Up @@ -380,83 +449,29 @@ def clean(self):
return self.cleaned_data


class IrodsDataRequestForm(forms.ModelForm):
class IrodsDataRequestForm(IrodsDataRequestValidateMixin, forms.ModelForm):
"""Form for iRODS data request creation and editing"""

class Meta:
model = IrodsDataRequest
fields = ['path', 'description']

def __init__(self, *args, **kwargs):
def __init__(self, project=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if project:
self.project = Project.objects.filter(sodar_uuid=project).first()
self.fields['description'].required = False

def clean(self):
cleaned_data = super().clean()
irods_backend = get_backend_api('omics_irods')
# Remove trailing slashes as irodspython client does not recognize
# this as a collection
cleaned_data['path'] = cleaned_data['path'].rstrip('/')

old_request = IrodsDataRequest.objects.filter(
path=cleaned_data['path'],
status__in=[
IRODS_REQUEST_STATUS_ACTIVE,
IRODS_REQUEST_STATUS_FAILED,
],
).first()
if old_request and old_request != self.instance:
self.add_error('path', ERROR_MSG_EXISTING)
return cleaned_data

path_re = re.compile(
'^' + irods_backend.get_projects_path() + '/[0-9a-f]{2}/'
'(?P<project_uuid>[0-9a-f-]{36})/'
+ settings.IRODS_SAMPLE_COLL
+ '/study_(?P<study_uuid>[0-9a-f-]{36})/'
'assay_(?P<assay_uuid>[0-9a-f-]{36})/.+$'
)
match = re.search(
path_re,
cleaned_data['path'],
)
if not match:
self.add_error('path', ERROR_MSG_INVALID_PATH)
else:
try:
cleaned_data['project'] = Project.objects.get(
sodar_uuid=match.group('project_uuid')
)
except Project.DoesNotExist:
self.add_error('path', 'Project not found')
try:
Study.objects.get(
sodar_uuid=match.group('study_uuid'),
investigation__project__sodar_uuid=match.group(
'project_uuid'
),
)
except Study.DoesNotExist:
self.add_error('path', 'Study not found in project with UUID')
try:
Assay.objects.get(
sodar_uuid=match.group('assay_uuid'),
study__sodar_uuid=match.group('study_uuid'),
)
except Assay.DoesNotExist:
self.add_error(
'path', 'Assay not found in this project with UUID'
)

with irods_backend.get_session() as irods:
if 'path' in cleaned_data and not (
irods.data_objects.exists(cleaned_data['path'])
or irods.collections.exists(cleaned_data['path'])
):
self.add_error(
'path',
'Path to collection or data object doesn\'t exist in iRODS',
)
cleaned_data['path'] = irods_backend.sanitize_path(cleaned_data['path'])
try:
self.validate_request_path(
irods_backend, self.project, self.instance, cleaned_data['path']
)
except Exception as ex:
self.add_error('path', str(ex))
return cleaned_data


Expand Down
61 changes: 8 additions & 53 deletions samplesheets/serializers.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
"""API view model serializers for the samplesheets app"""

import re

from django.conf import settings

from rest_framework import serializers

# Projectroles dependency
from projectroles.models import Project
from projectroles.plugins import get_backend_api
from projectroles.serializers import (
SODARProjectModelSerializer,
SODARNestedListSerializer,
SODARUserSerializer,
)

from samplesheets.forms import ERROR_MSG_EXISTING, ERROR_MSG_INVALID_PATH
from samplesheets.forms import IrodsDataRequestValidateMixin
from samplesheets.models import Investigation, Study, Assay, IrodsDataRequest


Expand Down Expand Up @@ -95,7 +90,9 @@ class Meta:
read_only_fields = fields


class IrodsDataRequestSerializer(SODARProjectModelSerializer):
class IrodsDataRequestSerializer(
IrodsDataRequestValidateMixin, SODARProjectModelSerializer
):
"""Serializer for the IrodsDataRequest model"""

user = SODARUserSerializer(read_only=True)
Expand All @@ -121,54 +118,12 @@ class Meta:
def validate_path(self, value):
irods_backend = get_backend_api('omics_irods')
path = irods_backend.sanitize_path(value)
path_re = re.compile(
'^' + irods_backend.get_projects_path() + '/[0-9a-f]{2}/'
'(?P<project_uuid>[0-9a-f-]{36})/'
+ settings.IRODS_SAMPLE_COLL
+ '/study_(?P<study_uuid>[0-9a-f-]{36})/'
'assay_(?P<assay_uuid>[0-9a-f-]{36})/.+$'
)

old_request = IrodsDataRequest.objects.filter(
path=path, status__in=['ACTIVE', 'FAILED']
).first()
if old_request and old_request != self.instance:
raise serializers.ValidationError(ERROR_MSG_EXISTING)

match = re.search(path_re, path)
if not match:
raise serializers.ValidationError(ERROR_MSG_INVALID_PATH)
try:
Project.objects.get(sodar_uuid=match.group('project_uuid'))
except Project.DoesNotExist:
raise serializers.ValidationError('Project not found')
try:
Study.objects.get(
sodar_uuid=match.group('study_uuid'),
investigation__project__sodar_uuid=match.group('project_uuid'),
)
except Study.DoesNotExist:
raise serializers.ValidationError(
'Study not found in project with UUID'
)
try:
Assay.objects.get(
sodar_uuid=match.group('assay_uuid'),
study__sodar_uuid=match.group('study_uuid'),
self.validate_request_path(
irods_backend, self.context['project'], self.instance, path
)
except Assay.DoesNotExist:
raise serializers.ValidationError(
'Assay not found in this project with UUID'
)

with irods_backend.get_session() as irods:
if not (
irods.data_objects.exists(path)
or irods.collections.exists(path)
):
raise serializers.ValidationError(
'Path to collection or data object doesn\'t exist in iRODS'
)
except Exception as ex:
raise serializers.ValidationError(str(ex))
return value

def create(self, validated_data):
Expand Down
12 changes: 12 additions & 0 deletions samplesheets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2473,6 +2473,12 @@ class IrodsDataRequestCreateView(
template_name = 'samplesheets/irods_request_form.html'
form_class = IrodsDataRequestForm

def get_form_kwargs(self):
"""Pass kwargs to form"""
kwargs = super().get_form_kwargs()
kwargs.update({'project': self.get_project().sodar_uuid})
return kwargs

def form_valid(self, form):
project = self.get_project()
# Create database object
Expand Down Expand Up @@ -2521,6 +2527,12 @@ def has_permission(self):
return False
return super().has_permission()

def get_form_kwargs(self):
"""Pass kwargs to form"""
kwargs = super().get_form_kwargs()
kwargs.update({'project': self.get_project().sodar_uuid})
return kwargs

def form_valid(self, form):
obj = form.save(commit=False)
obj.user = self.request.user
Expand Down

0 comments on commit cbf108d

Please sign in to comment.