diff --git a/config/settings/base.py b/config/settings/base.py index 81499eeb..fa85385b 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -308,6 +308,8 @@ "cdsa:records:studies", "cdsa:records:workspaces", "cdsa:records:user_access", + "dbgap:records:index", + "dbgap:records:applications", ] # django-dbbackup diff --git a/primed/dbgap/tables.py b/primed/dbgap/tables.py index cfff4d04..2e075bcd 100644 --- a/primed/dbgap/tables.py +++ b/primed/dbgap/tables.py @@ -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",) diff --git a/primed/dbgap/tests/test_views.py b/primed/dbgap/tests/test_views.py index 66ae2c8b..8020b2d2 100644 --- a/primed/dbgap/tests/test_views.py +++ b/primed/dbgap/tests/test_views.py @@ -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) diff --git a/primed/dbgap/urls.py b/primed/dbgap/urls.py index 9b0395eb..e49c633a 100644 --- a/primed/dbgap/urls.py +++ b/primed/dbgap/urls.py @@ -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)), ] diff --git a/primed/dbgap/views.py b/primed/dbgap/views.py index faffbbbf..b01f4bec 100644 --- a/primed/dbgap/views.py +++ b/primed/dbgap/views.py @@ -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 @@ -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 diff --git a/primed/templates/dbgap/dbgapapplication_records.html b/primed/templates/dbgap/dbgapapplication_records.html new file mode 100644 index 00000000..89cd6019 --- /dev/null +++ b/primed/templates/dbgap/dbgapapplication_records.html @@ -0,0 +1,15 @@ +{% extends "anvil_consortium_manager/base.html" %} +{% load render_table from django_tables2 %} + +{% block title %}dbGaP application records{% endblock %} + +{% block content %} +

dbGaP application records

+ +
+

The following table shows the list of PRIMED coordinated dbGaP applications that the Coordinating Center is tracking as of {% now "SHORT_DATETIME_FORMAT" %}.

+
+ +{% render_table table %} + +{% endblock content %} diff --git a/primed/templates/dbgap/records_index.html b/primed/templates/dbgap/records_index.html new file mode 100644 index 00000000..23d72e8e --- /dev/null +++ b/primed/templates/dbgap/records_index.html @@ -0,0 +1,27 @@ +{% extends "anvil_consortium_manager/base.html" %} + +{% block title %}dbGaP records{% endblock %} + +{% block content %} +

dbGaP records

+ + +
+ +
+
+

+
+
+

dbGaP coordinated applications

+

View dbGaP coordinated applications for PRIMED.

+ + View applications + +
+
+ +
+ + +{% endblock content %}