Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a public dbGaP records page #264

Merged
merged 2 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@
"cdsa:records:studies",
"cdsa:records:workspaces",
"cdsa:records:user_access",
"dbgap:records:index",
"dbgap:records:applications",
]

# django-dbbackup
Expand Down
37 changes: 37 additions & 0 deletions primed/dbgap/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,40 @@ class Meta:
model = models.dbGaPDataAccessRequest
fields = ("dbgap_dac", "dbgap_current_status", "total")
attrs = {"class": "table table-sm"}


class dbGaPApplicationRecordsTable(tables.Table):
"""Class to render a publicly-viewable table of dbGaPApplication objects."""

dbgap_project_id = tables.columns.Column()
principal_investigator = tables.columns.Column(
verbose_name="Application PI",
accessor="principal_investigator__name",
)
principal_investigator__study_sites = tables.columns.ManyToManyColumn(
verbose_name="Study site(s)",
)
number_approved_dars = tables.columns.ManyToManyColumn(
accessor="dbgapdataaccesssnapshot_set",
filter=lambda qs: qs.filter(is_most_recent=True),
verbose_name="Number of approved DARs",
transform=lambda obj: obj.dbgapdataaccessrequest_set.approved().count(),
)
number_requested_dars = tables.columns.ManyToManyColumn(
accessor="dbgapdataaccesssnapshot_set",
filter=lambda qs: qs.filter(is_most_recent=True),
verbose_name="Number of requested DARs",
transform=lambda obj: obj.dbgapdataaccessrequest_set.count(),
)
last_update = ManyToManyDateTimeColumn(
accessor="dbgapdataaccesssnapshot_set",
filter=lambda qs: qs.filter(is_most_recent=True),
)

class Meta:
model = models.dbGaPApplication
fields = (
"dbgap_project_id",
"principal_investigator",
)
order_by = ("dbgap_project_id",)
84 changes: 84 additions & 0 deletions primed/dbgap/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4216,3 +4216,87 @@ def test_context_errors_table_no_dar_has_access(self):
audit.dbGaPWorkspaceAccessAudit.ERROR_HAS_ACCESS,
)
self.assertIsNotNone(table.rows[0].get_cell_value("action"))


class dbGaPRecordsIndexTest(TestCase):
"""Tests for the dbGaPRecordsIndex view."""

def setUp(self):
"""Set up test class."""
self.factory = RequestFactory()
# Create a user with both view and edit permission.
self.user = User.objects.create_user(username="test", password="test")

def get_url(self, *args):
"""Get the url for the view being tested."""
return reverse("dbgap:records:index", args=args)

def test_status_code_not_logged_in(self):
"View redirects to login view when user is not logged in."
response = self.client.get(self.get_url())
self.assertEqual(response.status_code, 200)

def test_status_code_user_logged_in(self):
"""Returns successful response code."""
self.client.force_login(self.user)
response = self.client.get(self.get_url())
self.assertEqual(response.status_code, 200)

def test_links(self):
"""response includes the correct links."""
self.client.force_login(self.user)
response = self.client.get(self.get_url())
self.assertContains(response, reverse("dbgap:records:applications"))
# In case we add similar public views later.
# self.assertContains(response, reverse("cdsa:records:user_access"))
# self.assertContains(response, reverse("cdsa:records:workspaces"))


class dbGaPApplicationRecordsListTest(TestCase):
"""Tests for the dbGaPApplicationRecordsList view."""

def setUp(self):
"""Set up test class."""
self.factory = RequestFactory()
# Create a user with both view and edit permission.
self.user = User.objects.create_user(username="test", password="test")

def get_url(self, *args):
"""Get the url for the view being tested."""
return reverse("dbgap:records:applications", args=args)

def test_status_code_not_logged_in(self):
"View redirects to login view when user is not logged in."
# Need a client for redirects.
response = self.client.get(self.get_url())
self.assertEqual(response.status_code, 200)

def test_status_code_user_logged_in(self):
"""Returns successful response code."""
self.client.force_login(self.user)
response = self.client.get(self.get_url())
self.assertEqual(response.status_code, 200)

def test_table_class(self):
"""The table is the correct class."""
self.client.force_login(self.user)
response = self.client.get(self.get_url())
self.assertIn("table", response.context_data)
self.assertIsInstance(
response.context_data["table"], tables.dbGaPApplicationRecordsTable
)

def test_table_no_rows(self):
"""No rows are shown if there are no dbGaPApplications objects."""
self.client.force_login(self.user)
response = self.client.get(self.get_url())
self.assertIn("table", response.context_data)
self.assertEqual(len(response.context_data["table"].rows), 0)

def test_table_three_rows(self):
"""Three rows are shown if there are three dbGaPApplication objects."""
factories.dbGaPApplicationFactory.create_batch(3)
self.client.force_login(self.user)
response = self.client.get(self.get_url())
self.assertIn("table", response.context_data)
self.assertEqual(len(response.context_data["table"].rows), 3)
15 changes: 15 additions & 0 deletions primed/dbgap/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,23 @@
],
"workspaces",
)

records_patterns = (
[
path("", views.dbGaPRecordsIndex.as_view(), name="index"),
path(
"applications/",
views.dbGaPApplicationRecords.as_view(),
name="applications",
),
],
"records",
)


urlpatterns = [
path("studies/", include(dbgap_study_accession_patterns)),
path("applications/", include(dbgap_application_patterns)),
path("workspaces/", include(dbgap_workspace_patterns)),
path("records/", include(records_patterns)),
]
22 changes: 21 additions & 1 deletion primed/dbgap/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@
from django.http import Http404
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, FormView, UpdateView
from django.views.generic import (
CreateView,
DetailView,
FormView,
TemplateView,
UpdateView,
)
from django_tables2 import SingleTableMixin, SingleTableView
from django_tables2.export.views import ExportMixin

Expand Down Expand Up @@ -570,3 +576,17 @@ def get_context_data(self, **kwargs):
context["needs_action_table"] = data_access_audit.get_needs_action_table()
context["data_access_audit"] = data_access_audit
return context


class dbGaPRecordsIndex(TemplateView):
"""Index page for dbGaP records."""

template_name = "dbgap/records_index.html"


class dbGaPApplicationRecords(SingleTableView):
"""Display a public list of dbGaP applications."""

model = models.dbGaPApplication
template_name = "dbgap/dbgapapplication_records.html"
table_class = tables.dbGaPApplicationRecordsTable
15 changes: 15 additions & 0 deletions primed/templates/dbgap/dbgapapplication_records.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% extends "anvil_consortium_manager/base.html" %}
{% load render_table from django_tables2 %}

{% block title %}dbGaP application records{% endblock %}

{% block content %}
<h1>dbGaP application records</h1>

<div class="">
<p>The following table shows the list of PRIMED coordinated dbGaP applications that the Coordinating Center is tracking as of {% now "SHORT_DATETIME_FORMAT" %}.</p>
</div>

{% render_table table %}

{% endblock content %}
27 changes: 27 additions & 0 deletions primed/templates/dbgap/records_index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{% extends "anvil_consortium_manager/base.html" %}

{% block title %}dbGaP records{% endblock %}

{% block content %}
<h1>dbGaP records</h1>


<div class="row g-4 py-2 mx-4 row-cols-2">

<div class="col d-flex align-items-start">
<div class="icon-square text-dark flex-shrink-0 me-3">
<h2><span class="fa-solid fa-handshake-simple mx-2"></span></h2>
</div>
<div>
<h2>dbGaP coordinated applications</h2>
<p>View dbGaP coordinated applications for PRIMED.</p>
<a href="{% url 'dbgap:records:applications' %}" class="btn btn-primary">
View applications
</a>
</div>
</div>

</div>


{% endblock content %}