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 @@

This table shows accessors named for this signed agreement, their AnVIL account (if linked), and whether or not they are a member of the access group on AnVIL. Only accessors who are PRIMED members are included. An accessor must have linked their AnVIL account to be granted access to data on AnVIL. - If changes need to be made, please contact the CC.

{% render_table tables.0 %} +

If accessors need to be added or removed, please contact the CC at {{ DCC_CONTACT_EMAIL }}

+ @@ -140,11 +141,12 @@

This table shows uploaders named for this signed agreement, their AnVIL account (if linked), and whether or not they are a member of the upload group on AnVIL. Only uploaders who are PRIMED members are included. An uploaders must have linked their AnVIL account to be granted access to data on AnVIL. - If changes need to be made, please contact the CC.

{% render_table tables.1 %} +

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 @@

This table shows accessors named for this signed agreement, their AnVIL account (if linked), and whether or not they are a member of the access group on AnVIL. Only accessors who are PRIMED members are included. An accessor must have linked their AnVIL account to be granted access to data on AnVIL. - If changes need to be made, please contact the CC.

{% render_table table %} +

If accessors need to be added or removed, please contact the CC at {{ DCC_CONTACT_EMAIL }}

+ @@ -95,6 +96,7 @@

{% endblock after_panel %} + {% block action_buttons %} {% if show_update_button %}

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 @@

This table shows accessors named for this signed agreement, their AnVIL account (if linked), and whether or not they are a member of the access group on AnVIL. Only accessors who are PRIMED members are included. An accessor must have linked their AnVIL account to be granted access to data on AnVIL. - If changes need to be made, please contact the CC.

{% render_table table %} +

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 @@

This table shows collaborators named for this dbGaP application, their AnVIL account (if linked), and whether or not they are a member of the access group on AnVIL. Only collaborators who are PRIMED members are included. A collaborator must have linked their AnVIL account to be granted access to data on AnVIL. - If changes need to be made, please contact the CC.

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 @@

{% render_table tables.1 %} +

If collaborators need to be added or removed, please contact the CC at {{ DCC_CONTACT_EMAIL }}

+ @@ -93,12 +94,14 @@

{% endif %} - {% endblock after_panel %} +{% endblock after_panel %} + {% block action_buttons %}

{% 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 %} +
+ + {% csrf_token %} + {{ form|crispy }} + + +
+ +{% 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