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 view showing history for a DAR #417

Merged
merged 5 commits into from
Jan 31, 2024
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
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
Loading