Skip to content

Commit

Permalink
add detail view (#1961)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Jul 26, 2024
1 parent 871d27d commit 129c060
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 20 deletions.
1 change: 1 addition & 0 deletions isatemplates/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def clean(self):
self.add_error('file_upload', NO_INVESTIGATION_MSG)
if not study_found:
self.add_error('file_upload', NO_STUDY_MSG)
# TODO: Create with default settings, validate with altamISA
# Validate description
# NOTE: Uniqueness within custom templates checked on model level
if settings.ISATEMPLATES_ENABLE_CUBI_TEMPLATES:
Expand Down
3 changes: 1 addition & 2 deletions isatemplates/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ def get_object_link(self, model_str, uuid):
if not obj:
return None
if obj.__class__ == CookiecutterISATemplate:
# TODO: Change to detail view once implemented
return {
'url': reverse(
'isatemplates:update',
'isatemplates:detail',
kwargs={'cookiecutterisatemplate': obj.sodar_uuid},
),
'label': obj.description,
Expand Down
3 changes: 3 additions & 0 deletions isatemplates/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
# Allow viewing template list
rules.add_perm('isatemplates.view_list', rules.is_superuser)

# Allow viewin template details
rules.add_perm('isatemplates.view_template', rules.is_superuser)

# Allow creation of templates
rules.add_perm('isatemplates.create_template', rules.is_superuser)

Expand Down
6 changes: 5 additions & 1 deletion isatemplates/templates/isatemplates/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ <h4>
<tbody>
{% for t in templates %}
<tr class="sodar-it-list-item">
<td>{{ t.description }}</td>
<td>
<a href="{% url 'isatemplates:detail' cookiecutterisatemplate=t.sodar_uuid %}">
{{ t.description }}
</a>
</td>
<td class="text-monospace text-nowrap">{{ t.name }}</td>
{% if t.user %}
<td>
Expand Down
96 changes: 96 additions & 0 deletions isatemplates/templates/isatemplates/template_detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{% extends 'projectroles/base.html' %}

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

{% block title %}
ISA-Tab Template "{{ template.description }}"
{% endblock title %}

{% block projectroles %}

<div class="row sodar-subtitle-container">
<h2>
<i class="iconify" data-icon="mdi:file-table"></i>
{{ object.description }}
</h2>
<a role="button" class="btn btn-secondary ml-auto pull-right"
href="{% url 'isatemplates:list' %}">
<i class="iconify" data-icon="mdi:arrow-left-circle"></i> Back
</a>
</div>

<div class="container-fluid sodar-page-container">
<div class="card" id="sodar-it-template-detail-info">
<div class="card-header">
<h4>
<i class="iconify" data-icon="mdi:info"></i>
Template Information
</h4>
</div>
<div class="card-body px-0">
<dl class="row pb-0">
<dt class="col-md-2">Description</dt>
<dd class="col-md-10">{{ object.description }}</dd>
<dt class="col-md-2">Name</dt>
<dd class="col-md-10">
<code>{{ object.name }}</code>
</dd>
<dt class="col-md-2">Active</dt>
{% if object.active %}
<dd class="col-md-10 text-success">True</dd>
{% else %}
<dd class="col-md-10 text-danger">False</dd>
{% endif %}
<dt class="col-md-2">
{% get_info_link 'User who last edited this template' as info_link %}
User {{ info_link | safe }}
</dt>
{% if object.user %}
<dd class="col-md-10">
{% get_user_html object.user as user_html %}
{{ user_html | safe }}
</dd>
{% else %}
<dd class="col-md-10 text-muted">N/A</dd>
{% endif %}
<dt class="col-md-2">Created</dt>
<dd class="col-md-10">{{ object.date_created | date:'Y-m-d H:i' }}</dd>
<dt class="col-md-2">Updated</dt>
<dd class="col-md-10">{{ object.date_modified | date:'Y-m-d H:i' }}</dd>
<dt class="col-md-2">UUID</dt>
<dd class="col-md-10"><code>{{ object.sodar_uuid }}</code></dd>
</dl>
</div>
</div>

<div class="card" id="sodar-it-template-detail-json">
<div class="card-header">
<h4>
<i class="iconify" data-icon="mdi:file-table-outline"></i>
File: cookiecutter.json
</h4>
</div>
<div class="card-body p-0">
<pre>{{ json_data }}</pre>
</div>
</div>

{% for f in files %}
<div class="card" class="sodar-it-template-detail-file"
id="sodar-it-template-detail-file-{{ f.sodar_uuid }}">
<div class="card-header">
<h4>
<i class="iconify" data-icon="mdi:file-table-outline"></i>
File: {{ f.file_name }}
</h4>
</div>
<div class="card-body p-0">
<pre>{{ f.content }}</pre>
</div>
</div>
{% endfor %}
</div>

{% endblock projectroles %}
12 changes: 12 additions & 0 deletions isatemplates/tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ def test_get_list(self):
self.assert_response(url, good_users, 200)
self.assert_response(url, bad_users, 302)

def test_get_detail(self):
"""Test ISATemplateDetailView GET"""
template = self.make_isa_template(TEMPLATE_NAME, TEMPLATE_DESC, {})
url = reverse(
'isatemplates:detail',
kwargs={'cookiecutterisatemplate': template.sodar_uuid},
)
good_users = [self.superuser]
bad_users = [self.anonymous, self.regular_user]
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')
Expand Down
57 changes: 57 additions & 0 deletions isatemplates/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,63 @@ def test_get_disable_cubi_templates(self):
self.assertEqual(response.context['cubi_templates'], None)


class TestISATemplateDetailView(ISATemplateViewTestBase):
"""Tests for ISATemplateDetailView"""

def setUp(self):
super().setUp()
# Set up template with data
with open(TEMPLATE_JSON_PATH, 'rb') as f:
json_data = json.load(f)
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:detail',
kwargs={'cookiecutterisatemplate': self.template.sodar_uuid},
)

def test_get(self):
"""Test ISATemplateDetailView GET"""
with self.login(self.user):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
context = response.context
self.assertEqual(context['object'], self.template)
# TODO: Ensure order once preserved
self.assertEqual(
json.loads(context['json_data']), self.template.json_data
)
self.assertEqual(len(context['files']), 3)
self.assertTrue(context['files'][0].file_name.startswith('i_'))
self.assertEqual(
context['files'][0].content,
self.file_data[context['files'][0].file_name],
)
self.assertTrue(context['files'][1].file_name.startswith('s_'))
self.assertEqual(
context['files'][1].content,
self.file_data[context['files'][1].file_name],
)
self.assertTrue(context['files'][2].file_name.startswith('a_'))
self.assertEqual(
context['files'][2].content,
self.file_data[context['files'][2].file_name],
)


class TestISATemplateCreateView(ISATemplateViewTestBase):
"""Tests for ISATemplateCreateView"""

Expand Down
5 changes: 5 additions & 0 deletions isatemplates/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
view=views.ISATemplateListView.as_view(),
name='list',
),
path(
route='<uuid:cookiecutterisatemplate>',
view=views.ISATemplateDetailView.as_view(),
name='detail',
),
path(
route='create',
view=views.ISATemplateCreateView.as_view(),
Expand Down
62 changes: 45 additions & 17 deletions isatemplates/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""UI views for the isatemplates app"""

import json

from cubi_isa_templates import _TEMPLATES as CUBI_TEMPLATES

from django.conf import settings
Expand All @@ -10,6 +12,7 @@
CreateView,
UpdateView,
DeleteView,
DetailView,
TemplateView,
)

Expand All @@ -25,23 +28,7 @@
APP_NAME = 'isatemplates'


class ISATemplateListView(LoggedInPermissionMixin, TemplateView):
"""CookiecutterISATemplate list view"""

permission_required = 'isatemplates.view_list'
template_name = 'isatemplates/list.html'

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['templates'] = CookiecutterISATemplate.objects.all().order_by(
'name'
)
context['cubi_templates'] = None
if settings.ISATEMPLATES_ENABLE_CUBI_TEMPLATES:
context['cubi_templates'] = sorted(
CUBI_TEMPLATES, key=lambda x: x.description.lower()
)
return context
# Mixins -----------------------------------------------------------------------


class ISATemplateModifyMixin:
Expand Down Expand Up @@ -80,6 +67,47 @@ def handle_modify(self, obj, action):
return reverse('isatemplates:list')


# Views ------------------------------------------------------------------------


class ISATemplateListView(LoggedInPermissionMixin, TemplateView):
"""CookiecutterISATemplate list view"""

permission_required = 'isatemplates.view_list'
template_name = 'isatemplates/list.html'

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['templates'] = CookiecutterISATemplate.objects.all().order_by(
'name'
)
context['cubi_templates'] = None
if settings.ISATEMPLATES_ENABLE_CUBI_TEMPLATES:
context['cubi_templates'] = sorted(
CUBI_TEMPLATES, key=lambda x: x.description.lower()
)
return context


class ISATemplateDetailView(LoggedInPermissionMixin, DetailView):
"""CookiecutterISATemplate details view"""

model = CookiecutterISATemplate
permission_required = 'isatemplates.view_template'
slug_url_kwarg = 'cookiecutterisatemplate'
slug_field = 'sodar_uuid'
template_name = 'isatemplates/template_detail.html'

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['json_data'] = json.dumps(self.object.json_data, indent=2)
files = CookiecutterISAFile.objects.filter(template=self.object)
context['files'] = sorted(
list(files), key=lambda x: 'isa'.index(x.file_name[0])
)
return context


class ISATemplateCreateView(
LoggedInPermissionMixin,
CurrentUserFormMixin,
Expand Down

0 comments on commit 129c060

Please sign in to comment.