Skip to content

Commit

Permalink
add template create view (#1961)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Jul 23, 2024
1 parent 54171c1 commit 594da04
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 19 deletions.
180 changes: 178 additions & 2 deletions isatemplates/forms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,180 @@
"""Forms for the isatemplates app"""

# TODO: Add template create/update form
# TODO: Copy file uploading from samplesheets
import json
import logging

from cubi_isa_templates import _TEMPLATES as CUBI_TEMPLATES

from django import forms
from django.conf import settings
from django.db import transaction

# Projectroles dependency
from projectroles.forms import MultipleFileField

# Samplesheets dependency
# NOTE: Importing for generic Zip file helpers, move elsewhere?
from samplesheets.io import SampleSheetIO, ARCHIVE_TYPES

from isatemplates.models import (
CookiecutterISATemplate,
CookiecutterISAFile,
FILE_PREFIXES,
)


logger = logging.getLogger(__name__)


# Local constants
FILE_SKIP_MSG = 'Skipping unrecognized file in template archive: {file_name}'


class ISATemplateForm(forms.ModelForm):
"""Form for importing and updating a custom ISA-Tab template."""

@classmethod
def _get_files_from_zip(cls, zip_file):
"""
Return template files from Zip archive.
:param zip_file: ZipFile object
:return: Dict
"""
ret = {'json': None, 'files': {}}
for path in [n for n in zip_file.namelist() if not n.endswith('/')]:
file_name = path.split('/')[-1]
if file_name == 'cookiecutter.json':
with zip_file.open(str(path), 'r') as f:
ret['json'] = json.load(f)
elif file_name[:2] in FILE_PREFIXES:
with zip_file.open(str(path), 'r') as f:
ret['files'][file_name] = f.read().decode('utf-8')
else:
logger.warning(FILE_SKIP_MSG.format(file_name=file_name))
return ret

@classmethod
def _get_files_from_multi(cls, files):
"""
Return template files from multi-file upload.
:param files: List
:return: Dict
"""
ret = {'json': None, 'files': {}}
for file in files:
if file.name == 'cookiecutter.json':
ret['json'] = json.load(file)
elif file.name[:2] in FILE_PREFIXES:
ret['files'][file.name] = file.read().decode('utf-8')
else:
logger.warning(FILE_SKIP_MSG.format(file_name=file.name))
return ret

file_upload = MultipleFileField(
allow_empty_file=False,
help_text='Zip archive or JSON/text files for an ISA-Tab template',
)

class Meta:
model = CookiecutterISATemplate
fields = ['file_upload', 'description', 'name', 'active']

def __init__(self, current_user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.current_user = current_user
self.isa_zip = None
self.fields['name'].required = False # Will be auto-generated if empty

def clean(self):
self.cleaned_data = super().clean()
sheet_io = SampleSheetIO()

# Check file_upload
files = self.files.getlist('file_upload')
# Zip archive upload
if len(files) == 1:
file = self.cleaned_data.get('file_upload')[0]
try:
self.isa_zip = sheet_io.get_zip_file(file)
except OSError as ex:
self.add_error('file_upload', str(ex))
return self.cleaned_data
# Multi-file checks
else:
json_found = False
inv_found = False
study_found = False
for file in files:
if file.content_type in ARCHIVE_TYPES:
self.add_error(
'file_upload',
'You can only upload one Zip archive at a time',
)
return self.cleaned_data
if not file.name.endswith('.json') and not file.name.endswith(
'.txt'
):
self.add_error(
'file_upload',
'Only a Zip archive or template JSON/txt files allowed',
)
return self.cleaned_data
if file.name == 'cookiecutter.json':
json_found = True
elif file.name.startswith('i_'):
inv_found = True
elif file.name.startswith('s_'):
study_found = True
if not json_found:
self.add_error(
'file_upload', 'File cookiecutter.json not found'
)
if not inv_found:
self.add_error('file_upload', 'Investigation file not found')
if not study_found:
self.add_error('file_upload', 'Study file not found')

# Check description
# NOTE: Uniqueness within custom templates checked on model level
if settings.ISATEMPLATES_ENABLE_CUBI_TEMPLATES:
cubi_descs = [t.description.lower() for t in CUBI_TEMPLATES]
if self.cleaned_data['description'].lower() in cubi_descs:
self.add_error(
'description',
'Template with identical description found in CUBI '
'templates',
)
return self.cleaned_data

@transaction.atomic
def save(self, *args, **kwargs):
# TODO: Add support for updating
if self.isa_zip: # Zip archive
file_data = self._get_files_from_zip(self.isa_zip)
else: # Multi-file
file_data = self._get_files_from_multi(
self.files.getlist('file_upload')
)
logger.debug('Saving ISA template..')
template = CookiecutterISATemplate.objects.create(
name=self.cleaned_data['name'],
description=self.cleaned_data['description'],
json_data=file_data['json'],
user=self.current_user,
)
logger.debug(
'Saved ISA template: {} ({})'.format(
template.name, template.sodar_uuid
)
)
for k, v in file_data['files'].items():
file = CookiecutterISAFile.objects.create(
template=template, file_name=k, content=v
)
logger.debug(
'Saved ISA template file: {} ({})'.format(k, file.sodar_uuid)
)
logger.debug('Template save OK')
return template
6 changes: 1 addition & 5 deletions isatemplates/templates/isatemplates/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<div class="row sodar-subtitle-container bg-white sticky-top">
<h2><i class="iconify" data-icon="mdi:file-table"></i> ISA-Tab Templates</h2>
<a role="button" class="btn btn-primary ml-auto"
href="#"> {# TODO: Set URL #}
href="{% url 'isatemplates:create' %}">
<i class="iconify" data-icon="mdi:upload"></i> Import Template
</a>
</div>
Expand Down Expand Up @@ -110,10 +110,6 @@ <h4>
<i class="iconify" data-icon="mdi:lead-pencil"></i>
Update Template
</a>
<a class="dropdown-item" href="#"> {# TODO #}
<i class="iconify" data-icon="mdi:refresh"></i>
Replace Template
</a>
<a class="dropdown-item" href="#"> {# TODO #}
<i class="iconify" data-icon="mdi:download"></i>
Export Template
Expand Down
38 changes: 38 additions & 0 deletions isatemplates/templates/isatemplates/template_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{% extends 'projectroles/base.html' %}

{% load rules %}
{% load crispy_forms_filters %}

{% block title %}
{% if object.pk %}Update{% else %}Import{% endif %} ISA-Tab Template
{% endblock title %}

{% block projectroles %}

<div class="row sodar-subtitle-container">
<h2>{% if object.pk %}Update{% else %}Import{% endif %} ISA-Tab Template</h2>
</div>

<div class="container-fluid sodar-page-container">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form | crispy }}
<div class="row">
<div class="btn-group ml-auto" role="group">
<a role="button" class="btn btn-secondary"
href="{% url 'isatemplates:list' %}">
<i class="iconify" data-icon="mdi:arrow-left-circle"></i> Cancel
</a>
<button type="submit" class="btn btn-primary sodar-btn-submit-once">
{% if object.pk %}
<i class="iconify" data-icon="mdi:check-bold"></i> Update
{% else %}
<i class="iconify" data-icon="mdi:upload"></i> Import
{% endif %}
</button>
</div>
</div>
</form>
</div>

{% endblock projectroles %}
Binary file added isatemplates/tests/templates/test_generic.zip
Binary file not shown.
Loading

0 comments on commit 594da04

Please sign in to comment.