diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fd5a58c1..6062e39c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,11 +28,11 @@ jobs:
backend: ["sqlite", "mariadb"]
mariadb-version: ["10.4"]
include:
- - python-version: 3.12 # Possible future version.
+ - python-version: "3.10" # Future ubuntu 22.04 upgrade.
backend: "mariadb"
- mariadb-version: "10.5"
+ mariadb-version: "10.6"
pip-recompile: true
- - python-version: 3.12 # Possible future version.
+ - python-version: 3.12 # Future ubuntu 24.04.01 upgrade.
backend: "mariadb"
mariadb-version: "10.11"
pip-recompile: true
diff --git a/add_cdsa_example_data.py b/add_cdsa_example_data.py
index eb26c72d..105233ba 100644
--- a/add_cdsa_example_data.py
+++ b/add_cdsa_example_data.py
@@ -37,11 +37,28 @@
dum = DataUseModifier.objects.get(abbreviation="NPU")
# Create some study sites.
-StudySiteFactory.create(short_name="CC", full_name="Coordinating Center")
-StudySiteFactory.create(short_name="CARDINAL", full_name="CARDINAL")
+try:
+ cc = StudySite.objects.get(short_name="CC")
+except StudySite.DoesNotExist:
+ cc = StudySiteFactory.create(short_name="CC", full_name="Coordinating Center")
+try:
+ cardinal = StudySite.objects.get(short_name="CARDINAL")
+except StudySite.DoesNotExist:
+ cardinal = StudySiteFactory.create(short_name="CARDINAL", full_name="CARDINAL")
+
# Create some studies.
-StudyFactory.create(short_name="Amish", full_name="Amish")
-StudyFactory.create(short_name="MESA", full_name="MESA")
+try:
+ amish = Study.objects.get(short_name="Amish")
+except Study.DoesNotExist:
+ amish = StudyFactory.create(short_name="Amish", full_name="Amish")
+try:
+ mesa = Study.objects.get(short_name="MESA")
+except Study.DoesNotExist:
+ mesa = StudyFactory.create(short_name="MESA", full_name="MESA")
+try:
+ aric = Study.objects.get(short_name="ARIC")
+except Study.DoesNotExist:
+ aric = Study.objects.create(short_name="ARIC", full_name="Atherosclerosis Risk in Communities")
# Create some CDSAs
cdsa_1001 = factories.MemberAgreementFactory.create(
@@ -51,7 +68,7 @@
signed_agreement__representative_role="Contact PI",
is_primary=True,
signed_agreement__version=v10,
- study_site=StudySite.objects.get(short_name="CC"),
+ study_site=cc,
)
GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1001.signed_agreement.anvil_access_group)
@@ -62,7 +79,7 @@
signed_agreement__representative_role="Contact PI",
is_primary=True,
signed_agreement__version=v10,
- study_site=StudySite.objects.get(short_name="CARDINAL"),
+ study_site=cardinal,
)
GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1002.signed_agreement.anvil_access_group)
@@ -73,7 +90,7 @@
signed_agreement__representative_role="Co-PI",
is_primary=False,
signed_agreement__version=v10,
- study_site=StudySite.objects.get(short_name="CARDINAL"),
+ study_site=cardinal,
)
GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1003.signed_agreement.anvil_access_group)
@@ -84,7 +101,7 @@
signed_agreement__representative_role="Co-I",
is_primary=False,
signed_agreement__version=v11,
- study_site=StudySite.objects.get(short_name="CARDINAL"),
+ study_site=cardinal,
)
GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1004.signed_agreement.anvil_access_group)
@@ -93,7 +110,7 @@
signed_agreement__representative=User.objects.get(name="Brackie Mitchell"),
signed_agreement__signing_institution="UMaryland",
signed_agreement__representative_role="Study PI",
- study=Study.objects.get(short_name="Amish"),
+ study=amish,
signed_agreement__version=v10,
)
GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1005.signed_agreement.anvil_access_group)
@@ -103,7 +120,7 @@
signed_agreement__representative=UserFactory.create(name="Robyn"),
signed_agreement__representative_role="DCC PI",
signed_agreement__signing_institution="UW",
- study=Study.objects.get(short_name="MESA"),
+ study=mesa,
signed_agreement__version=v10,
additional_limitations="This data can only be used for testing the app.",
requires_study_review=True,
@@ -116,7 +133,7 @@
signed_agreement__signing_institution="JHU",
signed_agreement__representative_role="Field Center PI",
is_primary=False,
- study=Study.objects.get(short_name="MESA"),
+ study=mesa,
signed_agreement__version=v10,
)
GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1007.signed_agreement.anvil_access_group)
@@ -127,7 +144,7 @@
signed_agreement__signing_institution="Lundquist",
signed_agreement__representative_role="Analysis Center PI",
is_primary=False,
- study=Study.objects.get(short_name="MESA"),
+ study=mesa,
signed_agreement__version=v10,
)
GroupGroupMembershipFactory.create(parent_group=cdsa_group, child_group=cdsa_1008.signed_agreement.anvil_access_group)
@@ -209,7 +226,7 @@
cdsa_workspace_1 = factories.CDSAWorkspaceFactory.create(
workspace__billing_project__name="demo-primed-cdsa",
workspace__name="DEMO_PRIMED_CDSA_MESA_1",
- study=Study.objects.get(short_name="MESA"),
+ study=mesa,
data_use_permission=dup,
)
GroupGroupMembershipFactory.create(
@@ -222,7 +239,7 @@
cdsa_workspace_2 = factories.CDSAWorkspaceFactory.create(
workspace__billing_project__name="demo-primed-cdsa",
workspace__name="DEMO_PRIMED_CDSA_MESA_2",
- study=Study.objects.get(short_name="MESA"),
+ study=mesa,
data_use_permission=dup,
additional_limitations="Additional limitations for workspace.",
)
@@ -233,6 +250,6 @@
cdsa_workspace_3 = factories.CDSAWorkspaceFactory.create(
workspace__billing_project__name="demo-primed-cdsa",
workspace__name="DEMO_PRIMED_CDSA_ARIC_1",
- study=Study.objects.create(short_name="ARIC", full_name="Atherosclerosis Risk in Communities"),
+ study=aric,
data_use_permission=dup,
)
diff --git a/add_dbgap_example_data.py b/add_dbgap_example_data.py
index 20a1054b..232b0790 100644
--- a/add_dbgap_example_data.py
+++ b/add_dbgap_example_data.py
@@ -1,7 +1,11 @@
# Temporary script to create some test data.
# Run with: python manage.py shell < add_cdsa_example_data.py
-from anvil_consortium_manager.tests.factories import GroupGroupMembershipFactory
+from anvil_consortium_manager.tests.factories import (
+ AccountFactory,
+ GroupAccountMembershipFactory,
+ GroupGroupMembershipFactory,
+)
from primed.dbgap import models
from primed.dbgap.tests import factories
@@ -9,9 +13,18 @@
from primed.users.tests.factories import UserFactory
# Studies
-fhs = StudyFactory.create(short_name="FHS", full_name="Framingham Heart Study")
-mesa = StudyFactory.create(short_name="MESA", full_name="Multi-Ethnic Study of Atherosclerosis")
-aric = StudyFactory.create(short_name="ARIC", full_name="Atherosclerosis Risk in Communities")
+try:
+ fhs = models.Study.objects.get(short_name="FHS")
+except models.Study.DoesNotExist:
+ fhs = StudyFactory.create(short_name="FHS", full_name="Framingham Heart Study")
+try:
+ mesa = models.Study.objects.get(short_name="MESA")
+except models.Study.DoesNotExist:
+ mesa = StudyFactory.create(short_name="MESA", full_name="Multi-Ethnic Study of Atherosclerosis")
+try:
+ aric = models.Study.objects.get(short_name="ARIC")
+except models.Study.DoesNotExist:
+ aric = StudyFactory.create(short_name="ARIC", full_name="Atherosclerosis Risk in Communities")
# dbGaP study accessions
dbgap_study_accession_fhs = factories.dbGaPStudyAccessionFactory.create(dbgap_phs=7, studies=[fhs])
@@ -124,3 +137,24 @@
parent_group=workspace_fhs_1.workspace.authorization_domains.first(),
child_group=dbgap_application_1.anvil_access_group,
)
+
+# Add the PI to the access group for the first application.
+GroupAccountMembershipFactory.create(
+ group=dbgap_application_1.anvil_access_group,
+ account__user=dbgap_application_1.principal_investigator,
+)
+# Add some collaborators for the first application.
+users = UserFactory.create_batch(3)
+dbgap_application_1.collaborators.add(users[0])
+GroupAccountMembershipFactory.create(
+ group=dbgap_application_1.anvil_access_group,
+ account__user=users[0],
+)
+# # Do not add as an collaborator so we can check auditing.
+# dbgap_application_1.collaborators.add(users[1])
+GroupAccountMembershipFactory.create(
+ group=dbgap_application_1.anvil_access_group,
+ account__user=users[1],
+)
+dbgap_application_1.collaborators.add(users[2])
+AccountFactory.create(user=users[2], verified=True)
diff --git a/primed/cdsa/tests/test_views.py b/primed/cdsa/tests/test_views.py
index eb5a2290..6015197d 100644
--- a/primed/cdsa/tests/test_views.py
+++ b/primed/cdsa/tests/test_views.py
@@ -2879,7 +2879,7 @@ def test_response_is_primary(self):
self.assertContains(response, "Primary?")
self.assertContains(
response,
- """
Primary?Yes """, # noqa: E501
+ """Primary?Yes """, # noqa: E501
html=True,
)
instance.is_primary = False
@@ -2888,7 +2888,7 @@ def test_response_is_primary(self):
self.assertContains(response, "Primary?")
self.assertContains(
response,
- """Primary?No """, # noqa: E501
+ """Primary?No """, # noqa: E501
html=True,
)
@@ -4698,7 +4698,7 @@ def test_response_is_primary(self):
self.assertContains(response, "Primary?")
self.assertContains(
response,
- """Primary?Yes """, # noqa: E501
+ """Primary?Yes """, # noqa: E501
html=True,
)
instance.is_primary = False
@@ -4707,7 +4707,7 @@ def test_response_is_primary(self):
self.assertContains(response, "Primary?")
self.assertContains(
response,
- """Primary?No """, # noqa: E501
+ """Primary?No """, # noqa: E501
html=True,
)
@@ -4720,7 +4720,7 @@ def test_response_requires_study_review(self):
# import ipdb; ipdb.set_trace()
self.assertContains(
response,
- """Study review required? Yes """, # noqa: E501
+ """Study review required? Yes """, # noqa: E501
html=True,
)
instance.requires_study_review = False
@@ -4729,7 +4729,7 @@ def test_response_requires_study_review(self):
self.assertContains(response, "Study review required?")
self.assertContains(
response,
- """Study review required? No """, # noqa: E501
+ """Study review required? No """, # noqa: E501
html=True,
)
diff --git a/primed/dbgap/tests/test_views.py b/primed/dbgap/tests/test_views.py
index 10c698dd..dad3f2f3 100644
--- a/primed/dbgap/tests/test_views.py
+++ b/primed/dbgap/tests/test_views.py
@@ -1452,6 +1452,7 @@ def test_staff_edit_links(self):
self.assertContains(
response, reverse("anvil_consortium_manager:managed_groups:detail", args=[self.obj.anvil_access_group.name])
)
+ self.assertContains(response, reverse("dbgap:dbgap_applications:update", args=[self.obj.dbgap_project_id]))
def test_staff_view_links(self):
"""No edit links if staff user only has view permission."""
@@ -1475,6 +1476,7 @@ def test_staff_view_links(self):
self.assertContains(
response, reverse("anvil_consortium_manager:managed_groups:detail", args=[self.obj.anvil_access_group.name])
)
+ self.assertNotContains(response, reverse("dbgap:dbgap_applications:update", args=[self.obj.dbgap_project_id]))
def test_links_pi(self):
"""Links seen by PI are correct."""
@@ -1500,6 +1502,7 @@ def test_links_pi(self):
self.assertNotContains(
response, reverse("anvil_consortium_manager:managed_groups:detail", args=[self.obj.anvil_access_group.name])
)
+ self.assertNotContains(response, reverse("dbgap:dbgap_applications:update", args=[self.obj.dbgap_project_id]))
def test_links_collaborators(self):
"""Links seen by collaborators are correct."""
@@ -1526,6 +1529,7 @@ def test_links_collaborators(self):
self.assertNotContains(
response, reverse("anvil_consortium_manager:managed_groups:detail", args=[self.obj.anvil_access_group.name])
)
+ self.assertNotContains(response, reverse("dbgap:dbgap_applications:update", args=[self.obj.dbgap_project_id]))
def test_table_classes(self):
"""The table classes are correct."""
diff --git a/primed/dbgap/views.py b/primed/dbgap/views.py
index 403d2ef3..8cb4f4a2 100644
--- a/primed/dbgap/views.py
+++ b/primed/dbgap/views.py
@@ -246,7 +246,8 @@ class dbGaPApplicationUpdate(AnVILConsortiumManagerStaffEditRequired, SuccessMes
model = models.dbGaPApplication
form_class = forms.dbGaPApplicationUpdateForm
- success_message = "dbGaP application successfully updated."
+ success_message = "dbGaP collaborators successfully updated."
+ template_name = "dbgap/dbgapapplication_update_collaborators.html"
def get_object(self, queryset=None):
queryset = self.get_queryset()
diff --git a/primed/primed_anvil/filters.py b/primed/primed_anvil/filters.py
index 6100777d..88a4a49b 100644
--- a/primed/primed_anvil/filters.py
+++ b/primed/primed_anvil/filters.py
@@ -2,9 +2,18 @@
from anvil_consortium_manager.models import Account
from django_filters import FilterSet
+from .models import Study
+
class AccountListFilter(FilterSet):
class Meta:
model = Account
fields = {"email": ["icontains"], "user__name": ["icontains"]}
form = FilterForm
+
+
+class StudyListFilter(FilterSet):
+ class Meta:
+ model = Study
+ fields = {"short_name": ["icontains"], "full_name": ["icontains"]}
+ form = FilterForm
diff --git a/primed/primed_anvil/tests/test_views.py b/primed/primed_anvil/tests/test_views.py
index e28f43bc..22f929b9 100644
--- a/primed/primed_anvil/tests/test_views.py
+++ b/primed/primed_anvil/tests/test_views.py
@@ -37,7 +37,7 @@
from primed.primed_anvil.tests.factories import AvailableDataFactory, StudyFactory
from primed.users.tests.factories import UserFactory
-from .. import models, tables, views
+from .. import filters, models, tables, views
from . import factories
# from .utils import AnVILAPIMockTestMixin
@@ -724,6 +724,66 @@ def test_view_with_two_objects(self):
self.assertIn("table", response.context_data)
self.assertEqual(len(response.context_data["table"].rows), 2)
+ def test_filterset_class(self):
+ self.model_factory.create()
+ request = self.factory.get(self.get_url())
+ request.user = self.user
+ response = self.get_view()(request)
+ self.assertIn("filter", response.context_data)
+ self.assertIsInstance(response.context_data["filter"], filters.StudyListFilter)
+
+ def test_view_with_filter_return_no_object(self):
+ self.model_factory.create(short_name="TEST", full_name="Test study")
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(), {"short_name__icontains": "abc"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 0)
+ response = self.client.get(self.get_url(), {"full_name__icontains": "abc"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 0)
+
+ def test_view_with_filter_returns_one_object_exact(self):
+ self.model_factory.create(short_name="TEST", full_name="Test Study")
+ self.model_factory.create(short_name="ABC", full_name="ABC Study")
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(), {"short_name__icontains": "TEST"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 1)
+ response = self.client.get(self.get_url(), {"full_name__icontains": "Test Study"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 1)
+
+ def test_view_with_filter_returns_one_object_case_insensitive(self):
+ self.model_factory.create(short_name="TEST", full_name="TEST Study")
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(), {"short_name__icontains": "test"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 1)
+ response = self.client.get(self.get_url(), {"full_name__icontains": "test study"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 1)
+
+ def test_view_with_filter_returns_one_object_contains(self):
+ self.model_factory.create(short_name="TEST", full_name="TEST Study")
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(), {"short_name__icontains": "TES"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 1)
+ response = self.client.get(self.get_url(), {"full_name__icontains": "TES"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 1)
+
+ def test_view_with_filter_returns_all_objects(self):
+ self.model_factory.create(short_name="Test 1", full_name="Test 1 Study")
+ self.model_factory.create(short_name="Test 2", full_name="Test 2 Study")
+ self.client.force_login(self.user)
+ response = self.client.get(self.get_url(), {"short_name__icontains": "Test"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 2)
+ response = self.client.get(self.get_url(), {"full_name__icontains": "Test"})
+ self.assertIn("table", response.context_data)
+ self.assertEqual(len(response.context_data["table"].rows), 2)
+
class StudySiteDetailTest(TestCase):
"""Tests for the StudySiteDetail view."""
diff --git a/primed/primed_anvil/views.py b/primed/primed_anvil/views.py
index 844f0160..0811ec9c 100644
--- a/primed/primed_anvil/views.py
+++ b/primed/primed_anvil/views.py
@@ -13,6 +13,7 @@
from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import Q
from django.views.generic import CreateView, DetailView, TemplateView
+from django_filters.views import FilterView
from django_tables2 import MultiTableMixin, SingleTableMixin, SingleTableView
from primed.cdsa.models import DataAffiliateAgreement, MemberAgreement
@@ -34,7 +35,7 @@
)
from primed.users.tables import UserTable
-from . import helpers, models, tables
+from . import filters, helpers, models, tables
User = get_user_model()
@@ -77,11 +78,15 @@ def get_tables(self):
)
-class StudyList(AnVILConsortiumManagerViewRequired, SingleTableView):
+class StudyList(AnVILConsortiumManagerViewRequired, SingleTableView, FilterView, autocomplete.Select2QuerySetView):
"""View to show a list of `Study`s."""
model = models.Study
table_class = tables.StudyTable
+ template_name = "primed_anvil/study_list.html"
+
+ filterset_class = filters.StudyListFilter
+ queryset = models.Study.objects.order_by("short_name")
class StudyCreate(AnVILConsortiumManagerStaffEditRequired, SuccessMessageMixin, CreateView):
diff --git a/primed/templates/cdsa/dataaffiliateagreement_detail.html b/primed/templates/cdsa/dataaffiliateagreement_detail.html
index 11756028..40ac0363 100644
--- a/primed/templates/cdsa/dataaffiliateagreement_detail.html
+++ b/primed/templates/cdsa/dataaffiliateagreement_detail.html
@@ -14,51 +14,51 @@
{% block panel %}
- - Coordinating Center ID
- {{ object.signed_agreement.cc_id }}
- - Representative
-
+
- Coordinating Center ID
- {{ object.signed_agreement.cc_id }}
+ - Representative
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.signed_agreement.representative.name }}
{% else %}
{{ object.signed_agreement.representative.name }}
{% endif %}
- - Representative role
- {{ object.signed_agreement.representative_role }}
- - Signing institution
- {{ object.signed_agreement.signing_institution }}
- - Primary?
-
+
- Representative role
- {{ object.signed_agreement.representative_role }}
+ - Signing institution
- {{ object.signed_agreement.signing_institution }}
+ - Primary?
-
{% if object.is_primary %}
Yes
{% else %}
No
{% endif %}
- - Study review required?
-
+
- Study review required?
-
{% if object.requires_study_review %}
Yes
{% else %}
No
{% endif %}
- - Agreement version
-
+
- Agreement version
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.signed_agreement.version }}
{% else %}
{{ object.signed_agreement.version }}
{% endif %}
- - Status
- {{ object.signed_agreement.get_status_display }}
- - Date signed
- {{ object.signed_agreement.date_signed }}
+ - Status
- {{ object.signed_agreement.get_status_display }}
+ - Date signed
- {{ object.signed_agreement.date_signed }}
- - Study
-
+
- Study
-
{{ object.study }}
- - AnVIL access group
-
+
- AnVIL access group
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.signed_agreement.anvil_access_group }}
{% else %}
{{ object.signed_agreement.anvil_access_group }}
{% endif %}
- - AnVIL upload group
-
+
- AnVIL upload group
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.anvil_upload_group }}
{% else %}
@@ -68,8 +68,8 @@
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
- - Date created
- {{ object.created }}
- - Date modified
- {{ object.modified }}
+ - Date created
- {{ object.created }}
+ - Date modified
- {{ object.modified }}
{% endif %}
@@ -112,11 +112,12 @@ If accessors need to be added or removed, please contact the CC at {{ DCC_CONTACT_EMAIL }}
+
@@ -140,11 +141,12 @@ If uploaders need to be added or removed, please contact the CC at {{ DCC_CONTACT_EMAIL }}
+
diff --git a/primed/templates/cdsa/memberagreement_detail.html b/primed/templates/cdsa/memberagreement_detail.html
index 277481ed..342cb9b0 100644
--- a/primed/templates/cdsa/memberagreement_detail.html
+++ b/primed/templates/cdsa/memberagreement_detail.html
@@ -14,41 +14,41 @@
{% block panel %}
- - Coordinating Center ID
- {{ object.signed_agreement.cc_id }}
- - Representative
-
+
- Coordinating Center ID
- {{ object.signed_agreement.cc_id }}
+ - Representative
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.signed_agreement.representative.name }}
{% else %}
{{ object.signed_agreement.representative.name }}
{% endif %}
- - Representative role
- {{ object.signed_agreement.representative_role }}
- - Signing institution
- {{ object.signed_agreement.signing_institution }}
- - Primary?
-
+
- Representative role
- {{ object.signed_agreement.representative_role }}
+ - Signing institution
- {{ object.signed_agreement.signing_institution }}
+ - Primary?
-
{% if object.is_primary %}
Yes
{% else %}
No
{% endif %}
- - Agreement version
-
+
- Agreement version
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.signed_agreement.version }}
{% else %}
{{ object.signed_agreement.version }}
{% endif %}
- - Status
- {{ object.signed_agreement.get_status_display }}
- - Date signed
- {{ object.signed_agreement.date_signed }}
+ - Status
- {{ object.signed_agreement.get_status_display }}
+ - Date signed
- {{ object.signed_agreement.date_signed }}
- - Study site
-
+
- Study site
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.study_site }}
{% else %}
{{ object.study_site }}
{% endif %}
- - AnVIL access group
-
+
- AnVIL access group
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.signed_agreement.anvil_access_group }}
{% else %}
@@ -58,8 +58,8 @@
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
- - Date created
- {{ object.created }}
- - Date modified
- {{ object.modified }}
+ - Date created
- {{ object.created }}
+ - Date modified
- {{ object.modified }}
{% endif %}
@@ -83,11 +83,12 @@ If accessors need to be added or removed, please contact the CC at {{ DCC_CONTACT_EMAIL }}
+
@@ -95,6 +96,7 @@
diff --git a/primed/templates/cdsa/nondataaffiliateagreement_detail.html b/primed/templates/cdsa/nondataaffiliateagreement_detail.html
index 0988060e..d89a9ba4 100644
--- a/primed/templates/cdsa/nondataaffiliateagreement_detail.html
+++ b/primed/templates/cdsa/nondataaffiliateagreement_detail.html
@@ -14,28 +14,28 @@
{% block panel %}
- - Coordinating Center ID
- {{ object.signed_agreement.cc_id }}
- - Representative
-
+
- Coordinating Center ID
- {{ object.signed_agreement.cc_id }}
+ - Representative
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.signed_agreement.representative.name }}
{% else %}
{{ object.signed_agreement.representative.name }}
{% endif %}
- - Representative role
- {{ object.signed_agreement.representative_role }}
- - Signing institution
- {{ object.signed_agreement.signing_institution }}
- - Agreement version
-
+
- Representative role
- {{ object.signed_agreement.representative_role }}
+ - Signing institution
- {{ object.signed_agreement.signing_institution }}
+ - Agreement version
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.signed_agreement.version }}
{% else %}
{{ object.signed_agreement.version }}
{% endif %}
- - Status
- {{ object.signed_agreement.get_status_display }}
- - Date signed
- {{ object.signed_agreement.date_signed }}
+ - Status
- {{ object.signed_agreement.get_status_display }}
+ - Date signed
- {{ object.signed_agreement.date_signed }}
- - Affiliation
- {{ object.affiliation }}
- - AnVIL access group
-
+
- Affiliation
- {{ object.affiliation }}
+ - AnVIL access group
-
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
{{ object.signed_agreement.anvil_access_group }}
{% else %}
@@ -45,8 +45,8 @@
{% if perms.anvil_consortium_manager.anvil_consortium_manager_staff_view %}
- - Date created
- {{ object.created }}
- - Date modified
- {{ object.modified }}
+ - Date created
- {{ object.created }}
+ - Date modified
- {{ object.modified }}
{% endif %}
@@ -71,11 +71,12 @@ If accessors need to be added or removed, please contact the CC at {{ DCC_CONTACT_EMAIL }}
+
diff --git a/primed/templates/dbgap/dbgapapplication_detail.html b/primed/templates/dbgap/dbgapapplication_detail.html
index dd3363fe..f92ed40a 100644
--- a/primed/templates/dbgap/dbgapapplication_detail.html
+++ b/primed/templates/dbgap/dbgapapplication_detail.html
@@ -50,7 +50,6 @@
It is the PI's responsibility to ensure that all collaborators meet the dbGaP definition of internal collaborators or trainees/postdocs
@@ -58,6 +57,8 @@
If collaborators need to be added or removed, please contact the CC at {{ DCC_CONTACT_EMAIL }}
+
@@ -93,12 +94,14 @@
{% if show_acm_edit_links %}
Update data access requests
+ Update collaborators
{% endif %}
{% if show_acm_view_links %}
Audit workspace access
diff --git a/primed/templates/dbgap/dbgapapplication_update_collaborators.html b/primed/templates/dbgap/dbgapapplication_update_collaborators.html
new file mode 100644
index 00000000..a0e43812
--- /dev/null
+++ b/primed/templates/dbgap/dbgapapplication_update_collaborators.html
@@ -0,0 +1,32 @@
+{% extends "anvil_consortium_manager/__object_detail.html" %}
+{% load crispy_forms_tags %}
+
+{% block title %}Update dbGaP collaborators{% endblock %}
+
+
+{% block panel %}
+
+ - dbGaP PI
-
+
+ {{ object.principal_investigator.name }}
+
+
+ - dbGaP Project ID
- {{ object.dbgap_project_id }}
+
+{% endblock panel %}
+
+{% block after_panel %}
+
+
+{% endblock after_panel %}
+
+
+{% block inline_javascript %}
+ {{ form.media }}
+{% endblock inline_javascript %}
diff --git a/primed/templates/primed_anvil/study_list.html b/primed/templates/primed_anvil/study_list.html
index 922a26bb..84f2a77a 100644
--- a/primed/templates/primed_anvil/study_list.html
+++ b/primed/templates/primed_anvil/study_list.html
@@ -1,11 +1,14 @@
{% extends "anvil_consortium_manager/base.html" %}
{% load render_table from django_tables2 %}
+{% load crispy_forms_tags %}
{% block title %}Study List{% endblock %}
{% block content %}
Studies
+{% crispy filter.form %}
+
{% render_table table %}
{% endblock content %}
diff --git a/primed/templates/users/user_detail.html b/primed/templates/users/user_detail.html
index c8e903fc..0261598e 100644
--- a/primed/templates/users/user_detail.html
+++ b/primed/templates/users/user_detail.html
@@ -124,6 +124,11 @@ {% if object == request.user %}My{% el
Consortium data sharing agreements
{% if signed_agreements %}
{% for agreement in signed_agreements %}
+ {% if object == agreement.representative %}
+
+ Representative: {{ agreement }}
+
+ {% endif %}
{% if object in agreement.accessors.all %}
Accessor: {{ agreement }}
diff --git a/primed/users/tests/test_views.py b/primed/users/tests/test_views.py
index 25720612..98e9d6b3 100644
--- a/primed/users/tests/test_views.py
+++ b/primed/users/tests/test_views.py
@@ -329,6 +329,19 @@ def test_cdsa_no_signed_agreements(self):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No CDSAs")
+ def test_cdsa_representative(self):
+ agreement = MemberAgreementFactory.create(signed_agreement__representative=self.user)
+ self.client.force_login(self.user)
+ response = self.client.get(self.user.get_absolute_url())
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "Representative", count=1)
+ self.assertNotContains(response, "No CDSAs")
+ self.assertContains(response, agreement.signed_agreement.cc_id)
+ self.assertContains(response, agreement.get_absolute_url())
+ self.assertIn("signed_agreements", response.context)
+ self.assertEqual(len(response.context["signed_agreements"]), 1)
+ self.assertIn(agreement.signed_agreement, response.context["signed_agreements"])
+
def test_cdsa_accessor_on_one_member_agreement(self):
agreement = MemberAgreementFactory.create()
agreement.signed_agreement.accessors.add(self.user)
diff --git a/primed/users/views.py b/primed/users/views.py
index 8631101a..26458b59 100644
--- a/primed/users/views.py
+++ b/primed/users/views.py
@@ -26,7 +26,7 @@ def get_context_data(self, **kwargs):
Q(principal_investigator=self.object) | Q(collaborators=self.object)
).distinct()
context["signed_agreements"] = SignedAgreement.objects.filter(
- Q(accessors=self.object) | Q(dataaffiliateagreement__uploaders=self.object)
+ Q(representative=self.object) | Q(accessors=self.object) | Q(dataaffiliateagreement__uploaders=self.object)
).distinct()
return context
diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt
index 1bf8767c..f0da5f00 100644
--- a/requirements/dev-requirements.txt
+++ b/requirements/dev-requirements.txt
@@ -87,7 +87,7 @@ markupsafe==2.1.5
# werkzeug
matplotlib-inline==0.1.6
# via ipython
-mypy==1.10.0
+mypy==1.10.1
# via -r requirements/dev-requirements.in
mypy-extensions==1.0.0
# via mypy
@@ -136,7 +136,7 @@ requests==2.32.3
# -c requirements/requirements.txt
# -c requirements/test-requirements.txt
# sphinx
-ruff==0.4.10
+ruff==0.5.0
# via -r requirements/dev-requirements.in
six==1.16.0
# via