Skip to content

Commit

Permalink
Merge pull request #417 from UW-GAC/feature/dar-history
Browse files Browse the repository at this point in the history
Add a view showing history for a DAR
  • Loading branch information
amstilp authored Jan 31, 2024
2 parents b6d1828 + 51e21b9 commit e57f6f5
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 19 deletions.
45 changes: 30 additions & 15 deletions primed/dbgap/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,10 @@ class dbGaPDataAccessRequestTable(tables.Table):
verbose_name=" dbGaP application",
linkify=lambda record: record.dbgap_data_access_snapshot.dbgap_application.get_absolute_url(),
)
dbgap_dar_id = tables.columns.Column(verbose_name="DAR")
dbgap_dar_id = tables.columns.Column(
verbose_name="DAR",
linkify=("dbgap:dars:history", {"dbgap_dar_id": tables.A("dbgap_dar_id")}),
)
dbgap_dac = tables.columns.Column(verbose_name="DAC")
dbgap_accession = dbGaPAccessionColumn(
accessor="get_dbgap_accession",
Expand Down Expand Up @@ -273,22 +276,11 @@ class Meta:
attrs = {"class": "table table-sm"}


class dbGaPDataAccessRequestBySnapshotTable(tables.Table):
class dbGaPDataAccessRequestBySnapshotTable(dbGaPDataAccessRequestTable):
"""Class to render a table of dbGaPDataAccessRequest objects for a specific dbGaPDataAccessSnapshot."""

dbgap_dar_id = tables.columns.Column(verbose_name="DAR")
dbgap_dac = tables.columns.Column(verbose_name="DAC")
dbgap_accession = dbGaPAccessionColumn(
accessor="get_dbgap_accession",
verbose_name="Accession",
order_by=(
"dbgap_phs",
"original_version",
"original_participant_set",
),
)
dbgap_consent_abbreviation = tables.columns.Column(verbose_name="Consent")
dbgap_current_status = tables.columns.Column(verbose_name="Current status")
dbgap_data_access_snapshot__dbgap_application__dbgap_project_id = None
dbgap_data_access_snapshot__created = None
matching_workspaces = tables.columns.Column(
accessor="get_dbgap_workspaces", orderable=False, default=" "
)
Expand Down Expand Up @@ -326,6 +318,29 @@ def render_matching_workspaces(self, value, record):
return html


class dbGaPDataAccessRequestHistoryTable(dbGaPDataAccessRequestTable):
"""Class to render a table of dbGaPDataAccessRequest history by dbgap_dar_id."""

dbgap_dar_id = tables.columns.Column(
verbose_name="DAR",
linkify=False,
)

class Meta:
model = models.dbGaPDataAccessRequest
fields = (
"dbgap_dar_id",
"dbgap_data_access_snapshot__created",
"dbgap_current_status",
"dbgap_data_access_snapshot__dbgap_application__dbgap_project_id",
"dbgap_dac",
"dbgap_accession",
"dbgap_consent_abbreviation",
)
order_by = ("-dbgap_data_access_snapshot__created",)
attrs = {"class": "table table-sm"}


class dbGaPDataAccessRequestSummaryTable(tables.Table):
"""Table intended to show a summary of data access requests, grouped by DAC and current status."""

Expand Down
47 changes: 47 additions & 0 deletions primed/dbgap/tests/test_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,53 @@ def test_ordering(self):
self.assertEqual(table.data[3], instance_1)


class dbGaPDataAccessRequestHistoryTest(TestCase):
model = models.dbGaPDataAccessRequest
model_factory = factories.dbGaPDataAccessRequestFactory
table_class = tables.dbGaPDataAccessRequestHistoryTable

def test_row_count_with_no_objects(self):
table = self.table_class(self.model.objects.all())
self.assertEqual(len(table.rows), 0)

def test_row_count_with_one_object(self):
self.model_factory.create()
table = self.table_class(self.model.objects.all())
self.assertEqual(len(table.rows), 1)

def test_row_count_with_two_objects(self):
self.model_factory.create_batch(2)
table = self.table_class(self.model.objects.all())
self.assertEqual(len(table.rows), 2)

def test_ordering(self):
"""Instances are ordered alphabetically by dbgap_application and dbgap_dar_id."""
dbgap_snapshot_1 = factories.dbGaPDataAccessSnapshotFactory.create(
created=timezone.now() - timedelta(weeks=5),
)
instance_1 = self.model_factory.create(
dbgap_data_access_snapshot=dbgap_snapshot_1,
)
dbgap_snapshot_2 = factories.dbGaPDataAccessSnapshotFactory.create(
created=timezone.now() - timedelta(weeks=4)
)
instance_2 = self.model_factory.create(
dbgap_dar_id=instance_1.dbgap_dar_id,
dbgap_data_access_snapshot=dbgap_snapshot_2,
)
dbgap_snapshot_3 = factories.dbGaPDataAccessSnapshotFactory.create(
created=timezone.now() - timedelta(weeks=3)
)
instance_3 = self.model_factory.create(
dbgap_dar_id=instance_1.dbgap_dar_id,
dbgap_data_access_snapshot=dbgap_snapshot_3,
)
table = self.table_class(self.model.objects.all())
self.assertEqual(table.data[0], instance_3)
self.assertEqual(table.data[1], instance_2)
self.assertEqual(table.data[2], instance_1)


class dbGaPDataAccessRequestBySnapshotTableTest(TestCase):
model = models.dbGaPDataAccessRequest
model_factory = factories.dbGaPDataAccessRequestFactory
Expand Down
116 changes: 115 additions & 1 deletion primed/dbgap/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3670,7 +3670,7 @@ def setUp(self):

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

def get_view(self):
"""Return the view being tested."""
Expand Down Expand Up @@ -3751,6 +3751,120 @@ def test_export(self):
)


class dbGaPDataAccessRequestHistoryTest(TestCase):
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")
self.user.user_permissions.add(
Permission.objects.get(
codename=AnVILProjectManagerAccess.STAFF_VIEW_PERMISSION_CODENAME
)
)

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

def get_view(self):
"""Return the view being tested."""
return views.dbGaPDataAccessRequestHistory.as_view()

def test_view_redirect_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(1))
self.assertRedirects(
response,
resolve_url(settings.LOGIN_URL) + "?next=" + self.get_url(1),
)

def test_status_code_with_user_permission(self):
"""Returns successful response code."""
instance = factories.dbGaPDataAccessRequestFactory.create()
self.client.force_login(self.user)
response = self.client.get(self.get_url(instance.dbgap_dar_id))
self.assertEqual(response.status_code, 200)

def test_access_without_user_permission(self):
"""Raises permission denied if user has no permissions."""
user_no_perms = User.objects.create_user(
username="test-none", password="test-none"
)
request = self.factory.get(self.get_url(1))
request.user = user_no_perms
with self.assertRaises(PermissionDenied):
self.get_view()(request, 1)

def test_dbgap_dar_id_does_not_exist(self):
"""Raises permission denied if user has no permissions."""
request = self.factory.get(self.get_url(1))
request.user = self.user
with self.assertRaises(Http404):
self.get_view()(request, 1)

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

def test_one_dars(self):
"""Table displays two dars."""
dar = factories.dbGaPDataAccessRequestFactory.create(dbgap_dar_id=1)
self.client.force_login(self.user)
response = self.client.get(self.get_url(1))
self.assertEqual(len(response.context_data["table"].rows), 1)
self.assertIn(dar, response.context_data["table"].data)

def test_two_dars(self):
"""Table displays two dars."""
dar_1 = factories.dbGaPDataAccessRequestFactory.create(dbgap_dar_id=1)
dar_2 = factories.dbGaPDataAccessRequestFactory.create(dbgap_dar_id=1)
self.client.force_login(self.user)
response = self.client.get(self.get_url(1))
self.assertEqual(len(response.context_data["table"].rows), 2)
self.assertIn(dar_1, response.context_data["table"].data)
self.assertIn(dar_2, response.context_data["table"].data)

def test_matching_dbgap_dar_id(self):
"""Only DARs with the same dbgap_dar_id are in the table."""
dar_1 = factories.dbGaPDataAccessRequestFactory.create(dbgap_dar_id=1)
dar_2 = factories.dbGaPDataAccessRequestFactory.create(dbgap_dar_id=2)
self.client.force_login(self.user)
response = self.client.get(self.get_url(1))
self.assertEqual(len(response.context_data["table"].rows), 1)
self.assertIn(dar_1, response.context_data["table"].data)
self.assertNotIn(dar_2, response.context_data["table"].data)

def test_table_ordering(self):
"""DARs from the most recent snapshot appear first."""
old_snapshot = factories.dbGaPDataAccessSnapshotFactory.create(
created=timezone.now() - timedelta(weeks=5),
is_most_recent=False,
)
dar_1 = factories.dbGaPDataAccessRequestFactory.create(
dbgap_dar_id=1, dbgap_data_access_snapshot=old_snapshot
)
new_snapshot = factories.dbGaPDataAccessSnapshotFactory.create(
created=timezone.now() - timedelta(weeks=1),
is_most_recent=True,
)
dar_2 = factories.dbGaPDataAccessRequestFactory.create(
dbgap_dar_id=1, dbgap_data_access_snapshot=new_snapshot
)
self.client.force_login(self.user)
response = self.client.get(self.get_url(1))
self.assertEqual(len(response.context_data["table"].rows), 2)
self.assertEqual(dar_2, response.context_data["table"].rows[0].record)
self.assertEqual(dar_1, response.context_data["table"].rows[1].record)


class dbGaPApplicationAuditTest(TestCase):
"""Tests for the dbGaPApplicationAudit view."""

Expand Down
14 changes: 13 additions & 1 deletion primed/dbgap/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@
"dbgap_data_access_snapshots",
)

data_access_request_patterns = (
[
path("current/", views.dbGaPDataAccessRequestList.as_view(), name="current"),
path(
"history/<int:dbgap_dar_id>",
views.dbGaPDataAccessRequestHistory.as_view(),
name="history",
),
],
"dars",
)
dbgap_application_patterns = (
[
path("", views.dbGaPApplicationList.as_view(), name="list"),
Expand All @@ -50,7 +61,7 @@
views.dbGaPDataAccessSnapshotCreateMultiple.as_view(),
name="update_dars",
),
path("dars/", views.dbGaPDataAccessRequestList.as_view(), name="dars"),
# path("dars/", views.dbGaPDataAccessRequestList.as_view(), name="dars"),
path(
"<int:dbgap_project_id>/",
views.dbGaPApplicationDetail.as_view(),
Expand Down Expand Up @@ -93,6 +104,7 @@
urlpatterns = [
path("studies/", include(dbgap_study_accession_patterns)),
path("applications/", include(dbgap_application_patterns)),
path("dars/", include(data_access_request_patterns)),
path("workspaces/", include(dbgap_workspace_patterns)),
path("records/", include(records_patterns)),
]
35 changes: 35 additions & 0 deletions primed/dbgap/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,41 @@ def get_table_data(self):
)


class dbGaPDataAccessRequestHistory(
AnVILConsortiumManagerStaffViewRequired, ExportMixin, SingleTableView
):
"""View to show the history of a given DAR."""

model = models.dbGaPDataAccessRequest
table_class = tables.dbGaPDataAccessRequestHistoryTable
template_name = "dbgap/dbgapdataaccessrequest_history.html"

def get_dbgap_dar_id(self):
return self.kwargs.get("dbgap_dar_id")

def get(self, request, *args, **kwargs):
self.dbgap_dar_id = self.get_dbgap_dar_id()
return super().get(request, *args, **kwargs)

def get_table_data(self):
qs = self.get_queryset().filter(
dbgap_dar_id=self.dbgap_dar_id,
)
if not qs.count():
raise Http404("No DARs found matching the query.")
return qs

def get_table_kwargs(self):
return {
"order_by": "-dbgap_data_access_snapshot__created",
}

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["dbgap_dar_id"] = self.dbgap_dar_id
return context


class dbGaPApplicationAudit(AnVILConsortiumManagerStaffViewRequired, DetailView):
"""View to show audit results for a `dbGaPApplication`."""

Expand Down
15 changes: 15 additions & 0 deletions primed/templates/dbgap/dbgapdataaccessrequest_history.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 DAR history{% endblock %}

{% block content %}
<h1>dbGaP DAR history: {{ dbgap_dar_id }}</h1>

<p>
The table below shows all snapshots that have a record of this DAR.
</p>

{% render_table table %}

{% endblock content %}
2 changes: 1 addition & 1 deletion primed/templates/dbgap/dbgapdataaccessrequest_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<h1>Current dbGaP data access requests</h1>

<div class="container text-end">
<a class="btn btn-secondary" href="{% url 'dbgap:dbgap_applications:dars' %}?_export=tsv" role="button">Export <i class="bi bi-download ms-1"></i></a>
<a class="btn btn-secondary" href="{% url 'dbgap:dars:current' %}?_export=tsv" role="button">Export <i class="bi bi-download ms-1"></i></a>
</div>

{% render_table table %}
Expand Down
2 changes: 1 addition & 1 deletion primed/templates/dbgap/nav_items.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<a class="dropdown-item" href="{% url 'dbgap:dbgap_applications:list' %}">List dbGaP applications</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'dbgap:dbgap_applications:dars' %}">List all current DARs</a>
<a class="dropdown-item" href="{% url 'dbgap:dars:current' %}">List all current DARs</a>
</li>
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_edit %}
<li>
Expand Down

0 comments on commit e57f6f5

Please sign in to comment.