Skip to content

Commit

Permalink
add export view (#1961)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Jul 26, 2024
1 parent f411d52 commit a20ed6c
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 7 deletions.
3 changes: 2 additions & 1 deletion isatemplates/templates/isatemplates/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ <h4>
<i class="iconify" data-icon="mdi:lead-pencil"></i>
Update Template
</a>
<a class="dropdown-item" href="#"> {# TODO #}
<a class="dropdown-item"
href="{% url 'isatemplates:export' cookiecutterisatemplate=t.sodar_uuid %}">
<i class="iconify" data-icon="mdi:download"></i>
Export Template
</a>
Expand Down
29 changes: 24 additions & 5 deletions isatemplates/tests/test_permissions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for UI view permissions in the isatemplates app"""

from django.test import override_settings
from django.urls import reverse

# Projectroles dependency
Expand All @@ -21,10 +22,16 @@ def test_get_list(self):
"""Test ISATemplateListView GET"""
url = reverse('isatemplates:list')
good_users = [self.superuser]
bad_users = [self.anonymous, self.regular_user]
bad_users = [self.regular_user, self.anonymous]
self.assert_response(url, good_users, 200)
self.assert_response(url, bad_users, 302)

@override_settings(PROJECTROLES_ALLOW_ANONYMOUS=True)
def test_get_list_anon(self):
"""Test ISATemplateListView GET with anonymous access enabled"""
url = reverse('isatemplates:list')
self.assert_response(url, self.anonymous, 302)

def test_get_detail(self):
"""Test ISATemplateDetailView GET"""
template = self.make_isa_template(TEMPLATE_NAME, TEMPLATE_DESC, {})
Expand All @@ -33,15 +40,15 @@ def test_get_detail(self):
kwargs={'cookiecutterisatemplate': template.sodar_uuid},
)
good_users = [self.superuser]
bad_users = [self.anonymous, self.regular_user]
bad_users = [self.regular_user, self.anonymous]
self.assert_response(url, good_users, 200)
self.assert_response(url, bad_users, 302)

def test_get_create(self):
"""Test ISATemplateCreateView GET"""
url = reverse('isatemplates:create')
good_users = [self.superuser]
bad_users = [self.anonymous, self.regular_user]
bad_users = [self.regular_user, self.anonymous]
self.assert_response(url, good_users, 200)
self.assert_response(url, bad_users, 302)

Expand All @@ -53,7 +60,7 @@ def test_get_update(self):
kwargs={'cookiecutterisatemplate': template.sodar_uuid},
)
good_users = [self.superuser]
bad_users = [self.anonymous, self.regular_user]
bad_users = [self.regular_user, self.anonymous]
self.assert_response(url, good_users, 200)
self.assert_response(url, bad_users, 302)

Expand All @@ -65,6 +72,18 @@ def test_get_delete(self):
kwargs={'cookiecutterisatemplate': template.sodar_uuid},
)
good_users = [self.superuser]
bad_users = [self.anonymous, self.regular_user]
bad_users = [self.regular_user, self.anonymous]
self.assert_response(url, good_users, 200)
self.assert_response(url, bad_users, 302)

def test_get_export(self):
"""Test ISATemplateExportView GET"""
template = self.make_isa_template(TEMPLATE_NAME, TEMPLATE_DESC, {})
url = reverse(
'isatemplates:export',
kwargs={'cookiecutterisatemplate': template.sodar_uuid},
)
good_users = [self.superuser]
bad_users = [self.regular_user, self.anonymous]
self.assert_response(url, good_users, 200)
self.assert_response(url, bad_users, 302)
66 changes: 66 additions & 0 deletions isatemplates/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
]
TEMPLATE_NAME_UPDATE = 'updated_name'
TEMPLATE_DESC_UPDATE = 'Updated template name'
INVALID_UUID = '11111111-1111-1111-1111-111111111111'


class ISATemplateViewTestBase(
Expand Down Expand Up @@ -783,3 +784,68 @@ def test_post(self):
self.assertEqual(
ProjectEvent.objects.filter(event_name='template_delete').count(), 1
)


class TestISATemplateExportView(ISATemplateViewTestBase):
"""Tests for ISATemplateExportView"""

def setUp(self):
super().setUp()
# Set up template with data
with open(TEMPLATE_JSON_PATH, 'rb') as f:
json_data = f.read().decode('utf-8')
self.template = self.make_isa_template(
name=TEMPLATE_NAME,
description=TEMPLATE_DESC,
json_data=json_data,
user=self.user,
)
self.file_data = {}
for fn in ISA_FILE_NAMES:
fp = os.path.join(str(ISA_FILE_PATH), fn)
with open(fp, 'rb') as f:
fd = f.read().decode('utf-8')
self.file_data[fn] = fd
self.make_isa_file(
template=self.template, file_name=fn, content=fd
)
self.url = reverse(
'isatemplates:export',
kwargs={'cookiecutterisatemplate': self.template.sodar_uuid},
)

def test_get(self):
"""Test ISATemplateExportView GET"""
with self.login(self.user):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.get('Content-Disposition'),
'attachment; filename="{}"'.format(self.template.name + '.zip'),
)
f = io.BytesIO(response.content)
zf = zipfile.ZipFile(f, 'r')
self.assertIsNone(zf.testzip())
name_list = zf.namelist()
self.assertIn('cookiecutter.json', name_list)
self.assertEqual(
zf.read('cookiecutter.json').decode('utf-8'),
self.template.json_data,
)
for fn in ISA_FILE_NAMES:
fp = os.path.join(FILE_DIR, fn)
self.assertIn(fp, name_list)
f_obj = CookiecutterISAFile.objects.get(
template=self.template, file_name=fn
)
self.assertEqual(zf.read(fp).decode('utf-8'), f_obj.content)

def test_get_invalid_uuid(self):
"""Test GET with invalid UUID"""
url = reverse(
'isatemplates:export',
kwargs={'cookiecutterisatemplate': INVALID_UUID},
)
with self.login(self.user):
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
5 changes: 5 additions & 0 deletions isatemplates/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@
view=views.ISATemplateDeleteView.as_view(),
name='delete',
),
path(
route='export/<uuid:cookiecutterisatemplate>',
view=views.ISATemplateExportView.as_view(),
name='export',
),
]
33 changes: 32 additions & 1 deletion isatemplates/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"""UI views for the isatemplates app"""

import io
import os
import zipfile

from cubi_isa_templates import _TEMPLATES as CUBI_TEMPLATES

from django.conf import settings
from django.contrib import messages
from django.http import HttpResponse, Http404
from django.shortcuts import redirect
from django.urls import reverse
from django.views.generic import (
Expand All @@ -12,6 +17,7 @@
DeleteView,
DetailView,
TemplateView,
View,
)

# Projectroles dependency
Expand All @@ -24,6 +30,7 @@

# Local constants
APP_NAME = 'isatemplates'
FILE_DIR = '{{cookiecutter.__output_dir}}'


# Mixins -----------------------------------------------------------------------
Expand Down Expand Up @@ -159,4 +166,28 @@ def get_success_url(self):
return self.handle_modify(self.object, 'delete')


# TODO: Add export view
class ISATemplateExportView(LoggedInPermissionMixin, View):
"""CookiecutterISATemplate export view"""

permission_required = 'isatemplates.view_template'

def get(self, request, *args, **kwargs):
template = CookiecutterISATemplate.objects.filter(
sodar_uuid=kwargs.get('cookiecutterisatemplate')
).first()
if not template:
raise Http404('Template not found')
zip_name = template.name + '.zip'
zip_io = io.BytesIO()
zf = zipfile.ZipFile(zip_io, 'w', compression=zipfile.ZIP_DEFLATED)
zf.writestr('cookiecutter.json', template.json_data)
for f in template.files.all():
zf.writestr(os.path.join(FILE_DIR, f.file_name), f.content)
zf.close()
response = HttpResponse(
zip_io.getvalue(), content_type='application/zip'
)
response['Content-Disposition'] = 'attachment; filename="{}"'.format(
zip_name
)
return response

0 comments on commit a20ed6c

Please sign in to comment.